diff --git a/CMakeLists.txt b/CMakeLists.txt index 24555ad..edf13be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,6 +145,7 @@ add_definitions( -DFLAG_GZIP=2 -DADMIN_PASSWORD="asdf" -DGIT_HASH="blabla" + -DDEBUG_ANSI=1 -DESPFS_HEATSHRINK) add_executable(esp_vt100_firmware ${SOURCE_FILES}) diff --git a/Makefile b/Makefile index 0b351b6..3ada8cd 100644 --- a/Makefile +++ b/Makefile @@ -65,8 +65,15 @@ LIBS += esphttpd # compiler flags using during compilation of source files CFLAGS = -Os -ggdb -std=gnu99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \ -nostdlib -mlongcalls -mtext-section-literals -D__ets__ -DICACHE_FLASH \ - -Wno-address -Wno-unused -DHTTPD_MAX_BACKLOG_SIZE=8192 -DADMIN_PASSWORD=$(ADMIN_PASSWORD) \ - -DGIT_HASH='"$(shell git rev-parse --short HEAD)"' + -Wno-address -Wno-unused + +CFLAGS += -DHTTPD_MAX_BACKLOG_SIZE=8192 +CFLAGS += -DGIT_HASH='"$(shell git rev-parse --short HEAD)"' +CFLAGS += -DADMIN_PASSWORD=$(ADMIN_PASSWORD) + +# Debug logging +CFLAGS += -DDEBUG_ANSI=1 +CFLAGS += -DDEBUG_INPUT=1 # linker flags used to generate the main object file LDFLAGS = -nostdlib -Wl,--no-check-sections -u call_user_start -Wl,-static diff --git a/user/ansi_parser.c b/user/ansi_parser.c index ed593ca..8bd1f73 100644 --- a/user/ansi_parser.c +++ b/user/ansi_parser.c @@ -11,26 +11,27 @@ static const char _ansi_actions[] = { 0, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8, 1, 9, 1, 10, 1, - 11, 1, 12, 1, 13, 1, 14, 2, - 2, 5, 2, 10, 11, 2, 10, 12 - + 11, 1, 12, 1, 13, 1, 14, 1, + 15, 1, 16, 1, 17, 2, 2, 5, + 2, 10, 11, 2, 10, 12 }; static const char _ansi_eof_actions[] = { 0, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, - 13, 13, 13, 13, 0, 0, 0, 0, - 0, 0, 0, 0 + 13, 13, 13, 13, 13, 13, 0, 0, + 0, 0, 0, 0, 0, 0, 0 }; static const int ansi_start = 1; -static const int ansi_first_final = 28; +static const int ansi_first_final = 30; static const int ansi_error = 0; -static const int ansi_en_CSI_body = 4; -static const int ansi_en_OSC_body = 6; -static const int ansi_en_TITLE_body = 26; +static const int ansi_en_CSI_body = 5; +static const int ansi_en_OSC_body = 7; +static const int ansi_en_TITLE_body = 27; +static const int ansi_en_charsetcmd_body = 29; static const int ansi_en_main = 1; @@ -45,10 +46,39 @@ static void ICACHE_FLASH_ATTR resetParserCb(void *arg) { if (cs != ansi_start) { cs = ansi_start; - warn("Parser timeout, state reset"); + apars_reset_utf8buffer(); + ansi_warn("Parser timeout, state reset"); } } +#define HISTORY_LEN 16 + +#if DEBUG_ANSI +static char history[HISTORY_LEN + 1]; +#endif + +void ICACHE_FLASH_ATTR +apars_handle_badseq(void) +{ +#if DEBUG_ANSI + char buf1[HISTORY_LEN*3+2]; + char buf2[HISTORY_LEN*3+2]; + char *b1 = buf1; + char *b2 = buf2; + char c; + + for(int i=0;i 127) c = '.'; + b2 += sprintf(b2, "%c ", c); + } + + ansi_dbg("Context: %s", buf2); + ansi_dbg(" %s", buf1); +#endif +} + /** * \brief Linear ANSI chars stream parser * @@ -68,6 +98,7 @@ ansi_parser(const char *newdata, size_t len) // The CSI code is built here static char csi_leading; //!< Leading char, 0 if none static int csi_ni; //!< Number of the active digit + static int csi_cnt; //!< Digit count static int csi_n[CSI_N_MAX]; //!< Param digits static char csi_char; //!< CSI action char (end) static char osc_buffer[OSC_CHAR_MAX]; @@ -83,24 +114,35 @@ ansi_parser(const char *newdata, size_t len) // Init Ragel on the first run if (cs == -1) { -/* #line 87 "user/ansi_parser.c" */ +/* #line 118 "user/ansi_parser.c" */ { cs = ansi_start; } -/* #line 57 "user/ansi_parser.rl" */ +/* #line 87 "user/ansi_parser.rl" */ + +#if DEBUG_ANSI + memset(history, 0, sizeof(history)); +#endif } - // schedule state reset + // schedule state reset after idle timeout if (termconf->parser_tout_ms > 0) { os_timer_disarm(&resetTim); os_timer_setfn(&resetTim, resetParserCb, NULL); os_timer_arm(&resetTim, termconf->parser_tout_ms, 0); } +#if DEBUG_ANSI + for(int i=len; i 90 ) { - if ( 97 <= (*p) && (*p) <= 122 ) - goto tr4; + if ( (*p) < 60 ) { + if ( (*p) > 47 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto tr6; + } else if ( (*p) >= 40 ) + goto tr5; + } else if ( (*p) > 62 ) { + if ( (*p) > 90 ) { + if ( 97 <= (*p) && (*p) <= 122 ) + goto tr6; + } else if ( (*p) >= 65 ) + goto tr6; } else - goto tr4; + goto tr6; goto tr2; case 0: goto _out; case 3: - if ( 48 <= (*p) && (*p) <= 57 ) - goto tr8; + if ( 70 <= (*p) && (*p) <= 71 ) + goto tr10; goto tr2; -case 28: +case 30: if ( (*p) == 27 ) goto tr1; goto tr0; case 4: + if ( 48 <= (*p) && (*p) <= 57 ) + goto tr11; + goto tr2; +case 5: switch( (*p) ) { - case 59: goto tr11; - case 64: goto tr12; + case 59: goto tr14; + case 64: goto tr15; } if ( (*p) < 60 ) { if ( (*p) > 47 ) { if ( 48 <= (*p) && (*p) <= 57 ) - goto tr10; + goto tr13; } else if ( (*p) >= 32 ) - goto tr9; + goto tr12; } else if ( (*p) > 63 ) { if ( (*p) > 90 ) { if ( 96 <= (*p) && (*p) <= 122 ) - goto tr13; + goto tr16; } else if ( (*p) >= 65 ) - goto tr13; + goto tr16; } else - goto tr9; + goto tr12; goto tr2; -case 5: +case 6: if ( (*p) == 59 ) - goto tr11; + goto tr14; if ( (*p) < 64 ) { if ( 48 <= (*p) && (*p) <= 57 ) - goto tr10; + goto tr13; } else if ( (*p) > 90 ) { if ( 96 <= (*p) && (*p) <= 122 ) - goto tr13; + goto tr16; } else - goto tr13; + goto tr16; goto tr2; -case 29: +case 31: goto tr2; -case 30: +case 32: if ( (*p) == 59 ) - goto tr11; + goto tr14; if ( (*p) < 64 ) { if ( 48 <= (*p) && (*p) <= 57 ) - goto tr10; + goto tr13; } else if ( (*p) > 90 ) { if ( 96 <= (*p) && (*p) <= 122 ) - goto tr13; + goto tr16; } else - goto tr13; + goto tr16; goto tr2; -case 6: +case 7: switch( (*p) ) { - case 48: goto tr14; - case 66: goto tr15; - case 84: goto tr16; - case 87: goto tr17; + case 48: goto tr17; + case 66: goto tr18; + case 84: goto tr19; + case 87: goto tr20; } goto tr2; -case 7: +case 8: if ( (*p) == 59 ) - goto tr18; + goto tr21; goto tr2; -case 8: +case 9: switch( (*p) ) { - case 7: goto tr20; - case 27: goto tr21; + case 7: goto tr23; + case 27: goto tr24; } - goto tr19; -case 31: + goto tr22; +case 33: switch( (*p) ) { - case 7: goto tr20; - case 27: goto tr21; + case 7: goto tr23; + case 27: goto tr24; } - goto tr19; -case 9: + goto tr22; +case 10: if ( (*p) == 92 ) - goto tr22; + goto tr25; goto tr2; -case 32: +case 34: goto tr2; -case 10: +case 11: if ( (*p) == 84 ) - goto tr23; + goto tr26; goto tr2; -case 11: +case 12: if ( (*p) == 78 ) - goto tr24; + goto tr27; goto tr2; -case 12: +case 13: if ( 48 <= (*p) && (*p) <= 57 ) - goto tr25; + goto tr28; goto tr2; -case 13: +case 14: if ( (*p) == 61 ) - goto tr26; + goto tr29; goto tr2; -case 14: +case 15: switch( (*p) ) { - case 7: goto tr28; - case 27: goto tr29; + case 7: goto tr31; + case 27: goto tr32; } - goto tr27; -case 33: + goto tr30; +case 35: switch( (*p) ) { - case 7: goto tr28; - case 27: goto tr29; + case 7: goto tr31; + case 27: goto tr32; } - goto tr27; -case 15: - if ( (*p) == 92 ) - goto tr30; - goto tr2; + goto tr30; case 16: - if ( (*p) == 73 ) - goto tr31; + if ( (*p) == 92 ) + goto tr33; goto tr2; case 17: - if ( (*p) == 84 ) - goto tr32; + if ( (*p) == 73 ) + goto tr34; goto tr2; case 18: - if ( (*p) == 76 ) - goto tr33; + if ( (*p) == 84 ) + goto tr35; goto tr2; case 19: - if ( (*p) == 69 ) - goto tr34; + if ( (*p) == 76 ) + goto tr36; goto tr2; case 20: - if ( (*p) == 61 ) - goto tr35; + if ( (*p) == 69 ) + goto tr37; goto tr2; case 21: - if ( 48 <= (*p) && (*p) <= 57 ) - goto tr36; + if ( (*p) == 61 ) + goto tr38; goto tr2; case 22: - if ( (*p) == 59 ) - goto tr37; if ( 48 <= (*p) && (*p) <= 57 ) - goto tr36; + goto tr39; goto tr2; case 23: + if ( (*p) == 59 ) + goto tr40; if ( 48 <= (*p) && (*p) <= 57 ) - goto tr38; + goto tr39; goto tr2; case 24: + if ( 48 <= (*p) && (*p) <= 57 ) + goto tr41; + goto tr2; +case 25: switch( (*p) ) { - case 7: goto tr39; - case 27: goto tr40; + case 7: goto tr42; + case 27: goto tr43; } if ( 48 <= (*p) && (*p) <= 57 ) - goto tr38; + goto tr41; goto tr2; -case 25: +case 26: if ( (*p) == 92 ) - goto tr39; + goto tr42; goto tr2; -case 26: +case 27: switch( (*p) ) { - case 7: goto tr42; - case 27: goto tr43; + case 7: goto tr45; + case 27: goto tr46; } - goto tr41; -case 34: + goto tr44; +case 36: switch( (*p) ) { - case 7: goto tr42; - case 27: goto tr43; + case 7: goto tr45; + case 27: goto tr46; } - goto tr41; -case 27: + goto tr44; +case 28: if ( (*p) == 92 ) - goto tr44; + goto tr47; goto tr2; -case 35: +case 37: + goto tr2; +case 29: + goto tr48; +case 38: goto tr2; } @@ -319,101 +376,112 @@ case 35: tr0: cs = 1; goto f1; tr1: cs = 2; goto _again; tr3: cs = 3; goto _again; - tr9: cs = 5; goto f7; - tr10: cs = 5; goto f8; - tr11: cs = 5; goto f9; - tr14: cs = 7; goto _again; - tr18: cs = 8; goto _again; - tr19: cs = 8; goto f12; + tr4: cs = 4; goto _again; + tr12: cs = 6; goto f9; + tr13: cs = 6; goto f10; + tr14: cs = 6; goto f11; + tr17: cs = 8; goto _again; tr21: cs = 9; goto _again; - tr15: cs = 10; goto _again; - tr23: cs = 11; goto _again; - tr24: cs = 12; goto _again; - tr25: cs = 13; goto f8; - tr26: cs = 14; goto _again; - tr27: cs = 14; goto f12; + tr22: cs = 9; goto f14; + tr24: cs = 10; goto _again; + tr18: cs = 11; goto _again; + tr26: cs = 12; goto _again; + tr27: cs = 13; goto _again; + tr28: cs = 14; goto f10; tr29: cs = 15; goto _again; - tr16: cs = 16; goto _again; - tr31: cs = 17; goto _again; - tr32: cs = 18; goto _again; - tr33: cs = 19; goto _again; - tr34: cs = 20; goto _again; - tr17: cs = 21; goto _again; - tr36: cs = 22; goto f8; - tr37: cs = 23; goto f9; - tr38: cs = 24; goto f8; - tr40: cs = 25; goto _again; - tr41: cs = 26; goto f12; - tr43: cs = 27; goto _again; - tr4: cs = 28; goto f2; - tr5: cs = 28; goto f3; - tr6: cs = 28; goto f4; - tr7: cs = 28; goto f5; - tr8: cs = 28; goto f6; - tr13: cs = 29; goto f11; - tr12: cs = 30; goto f10; - tr20: cs = 31; goto f13; - tr35: cs = 32; goto f5; - tr22: cs = 32; goto f14; - tr30: cs = 32; goto f16; - tr39: cs = 32; goto f17; - tr28: cs = 33; goto f15; - tr42: cs = 34; goto f13; - tr44: cs = 35; goto f14; + tr30: cs = 15; goto f14; + tr32: cs = 16; goto _again; + tr19: cs = 17; goto _again; + tr34: cs = 18; goto _again; + tr35: cs = 19; goto _again; + tr36: cs = 20; goto _again; + tr37: cs = 21; goto _again; + tr20: cs = 22; goto _again; + tr39: cs = 23; goto f10; + tr40: cs = 24; goto f11; + tr41: cs = 25; goto f10; + tr43: cs = 26; goto _again; + tr44: cs = 27; goto f14; + tr46: cs = 28; goto _again; + tr5: cs = 30; goto f2; + tr6: cs = 30; goto f3; + tr7: cs = 30; goto f4; + tr8: cs = 30; goto f5; + tr9: cs = 30; goto f6; + tr10: cs = 30; goto f7; + tr11: cs = 30; goto f8; + tr16: cs = 31; goto f13; + tr15: cs = 32; goto f12; + tr23: cs = 33; goto f15; + tr38: cs = 34; goto f6; + tr25: cs = 34; goto f16; + tr33: cs = 34; goto f18; + tr42: cs = 34; goto f19; + tr31: cs = 35; goto f17; + tr45: cs = 36; goto f15; + tr47: cs = 37; goto f16; + tr48: cs = 38; goto f20; f1: _acts = _ansi_actions + 1; goto execFuncs; - f3: _acts = _ansi_actions + 3; goto execFuncs; - f7: _acts = _ansi_actions + 5; goto execFuncs; - f8: _acts = _ansi_actions + 7; goto execFuncs; - f9: _acts = _ansi_actions + 9; goto execFuncs; - f11: _acts = _ansi_actions + 11; goto execFuncs; + f4: _acts = _ansi_actions + 3; goto execFuncs; + f9: _acts = _ansi_actions + 5; goto execFuncs; + f10: _acts = _ansi_actions + 7; goto execFuncs; + f11: _acts = _ansi_actions + 9; goto execFuncs; + f13: _acts = _ansi_actions + 11; goto execFuncs; f0: _acts = _ansi_actions + 13; goto execFuncs; - f4: _acts = _ansi_actions + 15; goto execFuncs; - f5: _acts = _ansi_actions + 17; goto execFuncs; - f17: _acts = _ansi_actions + 19; goto execFuncs; - f12: _acts = _ansi_actions + 21; goto execFuncs; - f14: _acts = _ansi_actions + 23; goto execFuncs; - f16: _acts = _ansi_actions + 25; goto execFuncs; - f6: _acts = _ansi_actions + 27; goto execFuncs; - f2: _acts = _ansi_actions + 29; goto execFuncs; - f10: _acts = _ansi_actions + 31; goto execFuncs; - f13: _acts = _ansi_actions + 34; goto execFuncs; - f15: _acts = _ansi_actions + 37; goto execFuncs; + f5: _acts = _ansi_actions + 15; goto execFuncs; + f6: _acts = _ansi_actions + 17; goto execFuncs; + f19: _acts = _ansi_actions + 19; goto execFuncs; + f14: _acts = _ansi_actions + 21; goto execFuncs; + f16: _acts = _ansi_actions + 23; goto execFuncs; + f18: _acts = _ansi_actions + 25; goto execFuncs; + f8: _acts = _ansi_actions + 27; goto execFuncs; + f3: _acts = _ansi_actions + 29; goto execFuncs; + f7: _acts = _ansi_actions + 31; goto execFuncs; + f2: _acts = _ansi_actions + 33; goto execFuncs; + f20: _acts = _ansi_actions + 35; goto execFuncs; + f12: _acts = _ansi_actions + 37; goto execFuncs; + f15: _acts = _ansi_actions + 40; goto execFuncs; + f17: _acts = _ansi_actions + 43; goto execFuncs; execFuncs: _nacts = *_acts++; while ( _nacts-- > 0 ) { switch ( *_acts++ ) { case 0: -/* #line 76 "user/ansi_parser.rl" */ +/* #line 117 "user/ansi_parser.rl" */ { - apars_handle_plainchar((*p)); + if ((*p) != 0) { + apars_handle_plainchar((*p)); + } } break; case 1: -/* #line 83 "user/ansi_parser.rl" */ +/* #line 126 "user/ansi_parser.rl" */ { // Reset the CSI builder csi_leading = csi_char = 0; csi_ni = 0; + csi_cnt = 0; // Zero out digits for(int i = 0; i < CSI_N_MAX; i++) { csi_n[i] = 0; } - {cs = 4;goto _again;} + {cs = 5;goto _again;} } break; case 2: -/* #line 96 "user/ansi_parser.rl" */ +/* #line 140 "user/ansi_parser.rl" */ { csi_leading = (*p); } break; case 3: -/* #line 100 "user/ansi_parser.rl" */ +/* #line 144 "user/ansi_parser.rl" */ { + if (csi_cnt == 0) csi_cnt = 1; // x10 + digit if (csi_ni < CSI_N_MAX) { csi_n[csi_ni] = csi_n[csi_ni]*10 + ((*p) - '0'); @@ -421,30 +489,31 @@ execFuncs: } break; case 4: -/* #line 107 "user/ansi_parser.rl" */ +/* #line 152 "user/ansi_parser.rl" */ { + if (csi_cnt == 0) csi_cnt = 1; // handle case when first arg is empty + csi_cnt++; csi_ni++; } break; case 5: -/* #line 111 "user/ansi_parser.rl" */ +/* #line 158 "user/ansi_parser.rl" */ { csi_char = (*p); - - apars_handle_CSI(csi_leading, csi_n, csi_char); - + apars_handle_CSI(csi_leading, csi_n, csi_cnt, csi_char); {cs = 1;goto _again;} } break; case 6: -/* #line 119 "user/ansi_parser.rl" */ +/* #line 164 "user/ansi_parser.rl" */ { + ansi_warn("Invalid escape sequence discarded."); apars_handle_badseq(); {cs = 1;goto _again;} } break; case 7: -/* #line 136 "user/ansi_parser.rl" */ +/* #line 182 "user/ansi_parser.rl" */ { csi_ni = 0; @@ -456,32 +525,32 @@ execFuncs: osc_bi = 0; osc_buffer[0] = '\0'; - {cs = 6;goto _again;} + {cs = 7;goto _again;} } break; case 8: -/* #line 151 "user/ansi_parser.rl" */ +/* #line 197 "user/ansi_parser.rl" */ { osc_bi = 0; osc_buffer[0] = '\0'; - {cs = 26;goto _again;} + {cs = 27;goto _again;} } break; case 9: -/* #line 157 "user/ansi_parser.rl" */ +/* #line 203 "user/ansi_parser.rl" */ { apars_handle_OSC_SetScreenSize(csi_n[0], csi_n[1]); {cs = 1;goto _again;} } break; case 10: -/* #line 162 "user/ansi_parser.rl" */ +/* #line 208 "user/ansi_parser.rl" */ { osc_buffer[osc_bi++] = (*p); } break; case 11: -/* #line 166 "user/ansi_parser.rl" */ +/* #line 212 "user/ansi_parser.rl" */ { osc_buffer[osc_bi++] = '\0'; apars_handle_OSC_SetTitle(osc_buffer); @@ -489,7 +558,7 @@ execFuncs: } break; case 12: -/* #line 172 "user/ansi_parser.rl" */ +/* #line 218 "user/ansi_parser.rl" */ { osc_buffer[osc_bi++] = '\0'; apars_handle_OSC_SetButton(csi_n[0], osc_buffer); @@ -497,20 +566,42 @@ execFuncs: } break; case 13: -/* #line 204 "user/ansi_parser.rl" */ +/* #line 250 "user/ansi_parser.rl" */ { apars_handle_hashCode((*p)); {cs = 1;goto _again;} } break; case 14: -/* #line 209 "user/ansi_parser.rl" */ +/* #line 255 "user/ansi_parser.rl" */ { apars_handle_shortCode((*p)); {cs = 1;goto _again;} } break; -/* #line 514 "user/ansi_parser.c" */ + case 15: +/* #line 260 "user/ansi_parser.rl" */ + { + apars_handle_setXCtrls((*p)); + {cs = 1;goto _again;} + } + break; + case 16: +/* #line 265 "user/ansi_parser.rl" */ + { + // abuse the buffer for storing the leading char + osc_buffer[0] = (*p); + {cs = 29;goto _again;} + } + break; + case 17: +/* #line 271 "user/ansi_parser.rl" */ + { + apars_handle_characterSet(osc_buffer[0], (*p)); + {cs = 1;goto _again;} + } + break; +/* #line 605 "user/ansi_parser.c" */ } } goto _again; @@ -528,15 +619,16 @@ _again: while ( __nacts-- > 0 ) { switch ( *__acts++ ) { case 6: -/* #line 119 "user/ansi_parser.rl" */ +/* #line 164 "user/ansi_parser.rl" */ { + ansi_warn("Invalid escape sequence discarded."); apars_handle_badseq(); {cs = 1; if ( p == pe ) goto _test_eof; goto _again;} } break; -/* #line 540 "user/ansi_parser.c" */ +/* #line 632 "user/ansi_parser.c" */ } } } @@ -544,7 +636,7 @@ goto _again;} _out: {} } -/* #line 229 "user/ansi_parser.rl" */ +/* #line 295 "user/ansi_parser.rl" */ } diff --git a/user/ansi_parser.h b/user/ansi_parser.h index 10138f0..1b01c7e 100644 --- a/user/ansi_parser.h +++ b/user/ansi_parser.h @@ -8,14 +8,26 @@ #define CSI_N_MAX 3 #define OSC_CHAR_MAX TERM_TITLE_LEN -extern void apars_handle_badseq(void); extern void apars_handle_plainchar(char c); -extern void apars_handle_CSI(char leadchar, int *params, char keychar); +extern void apars_handle_CSI(char leadchar, int *params, int count, char keychar); extern void apars_handle_OSC_SetScreenSize(int rows, int cols); extern void apars_handle_OSC_SetButton(int num, const char *buffer); extern void apars_handle_OSC_SetTitle(const char *buffer); extern void apars_handle_shortCode(char c); extern void apars_handle_hashCode(char c); +extern void apars_handle_characterSet(char leadchar, char c); +extern void apars_handle_setXCtrls(char c); +extern void apars_reset_utf8buffer(void); + +// defined in the makefile +#if DEBUG_ANSI +#define ansi_warn warn +#define ansi_dbg warn +#else +#define ansi_warn(...) +#define ansi_dbg(...) +#endif + /** * \brief Linear ANSI chars stream parser @@ -32,4 +44,7 @@ extern void apars_handle_hashCode(char c); */ void ansi_parser(const char *newdata, size_t len); +/** This shows a short error message and prints the history (if any) */ +void apars_handle_badseq(void); + #endif // ANSI_PARSER_H diff --git a/user/ansi_parser.rl b/user/ansi_parser.rl index cabd79b..c2cf454 100644 --- a/user/ansi_parser.rl +++ b/user/ansi_parser.rl @@ -16,10 +16,39 @@ static void ICACHE_FLASH_ATTR resetParserCb(void *arg) { if (cs != ansi_start) { cs = ansi_start; - warn("Parser timeout, state reset"); + apars_reset_utf8buffer(); + ansi_warn("Parser timeout, state reset"); } } +#define HISTORY_LEN 16 + +#if DEBUG_ANSI +static char history[HISTORY_LEN + 1]; +#endif + +void ICACHE_FLASH_ATTR +apars_handle_badseq(void) +{ +#if DEBUG_ANSI + char buf1[HISTORY_LEN*3+2]; + char buf2[HISTORY_LEN*3+2]; + char *b1 = buf1; + char *b2 = buf2; + char c; + + for(int i=0;i 127) c = '.'; + b2 += sprintf(b2, "%c ", c); + } + + ansi_dbg("Context: %s", buf2); + ansi_dbg(" %s", buf1); +#endif +} + /** * \brief Linear ANSI chars stream parser * @@ -39,6 +68,7 @@ ansi_parser(const char *newdata, size_t len) // The CSI code is built here static char csi_leading; //!< Leading char, 0 if none static int csi_ni; //!< Number of the active digit + static int csi_cnt; //!< Digit count static int csi_n[CSI_N_MAX]; //!< Param digits static char csi_char; //!< CSI action char (end) static char osc_buffer[OSC_CHAR_MAX]; @@ -54,15 +84,26 @@ ansi_parser(const char *newdata, size_t len) // Init Ragel on the first run if (cs == -1) { %% write init; + +#if DEBUG_ANSI + memset(history, 0, sizeof(history)); +#endif } - // schedule state reset + // schedule state reset after idle timeout if (termconf->parser_tout_ms > 0) { os_timer_disarm(&resetTim); os_timer_setfn(&resetTim, resetParserCb, NULL); os_timer_arm(&resetTim, termconf->parser_tout_ms, 0); } +#if DEBUG_ANSI + for(int i=len; i] @SHORT_code) | + ([()*+-./] @CharsetCmd_start) | + (' ' [FG] @SetXCtrls) ) )+ $!errBadSeq; diff --git a/user/ansi_parser_callbacks.c b/user/ansi_parser_callbacks.c index 05ac348..3fa86f2 100644 --- a/user/ansi_parser_callbacks.c +++ b/user/ansi_parser_callbacks.c @@ -39,28 +39,53 @@ apars_handle_plainchar(char c) utf_j = 1; } else { - utf_collect[utf_j++] = c; - if (utf_j >= utf_i) { - screen_putchar(utf_collect); - utf_i = 0; - utf_j = 0; - memset(utf_collect, 0, 4); + if ((c & 0xC0) != 0x80) { + // bad UTF + ansi_warn("Bad UTF-8"); + apars_reset_utf8buffer(); + } + else { + utf_collect[utf_j++] = c; + if (utf_j >= utf_i) { + screen_putchar(utf_collect); + apars_reset_utf8buffer(); + } } } } else { + if (c == 14 || c == 15) { // pushIn, pushOut + //ansi_warn("char %d ignored", c); + // TODO implement for charset switching + return; + } + utf_collect[0] = c; utf_collect[1] = 0; // just to make sure it's closed... screen_putchar(utf_collect); } } -/** - * Bad sequence received, show warning - */ -void apars_handle_badseq(void) +void ICACHE_FLASH_ATTR +apars_reset_utf8buffer(void) +{ + utf_i = 0; + utf_j = 0; + memset(utf_collect, 0, 4); +} + +void ICACHE_FLASH_ATTR +apars_handle_characterSet(char leadchar, char c) +{ + // TODO implement for charset switching +// ansi_warn("NOIMPL charset cmd %c%c", leadchar, c); +} + +void ICACHE_FLASH_ATTR +apars_handle_setXCtrls(char c) { - warn("Invalid escape sequence discarded."); + // this does not seem to do anything, sent by some unix programs +// ansi_warn("NOIMPL Select %cbit ctrls", c=='F'? '7':'8'); } /** @@ -70,7 +95,7 @@ void apars_handle_badseq(void) * \param keychar - the char terminating the sequence */ void ICACHE_FLASH_ATTR -apars_handle_CSI(char leadchar, int *params, char keychar) +apars_handle_CSI(char leadchar, int *params, int count, char keychar) { /* Implemented codes (from Wikipedia) @@ -216,38 +241,66 @@ apars_handle_CSI(char leadchar, int *params, char keychar) // Query device status - reply "Device is OK" UART_WriteString(UART0, "\033[0n", UART_TIMEOUT_US); } + else { + ansi_warn("NOIMPL query %d", n1); + } break; // DECTCEM feature enable / disable case 'h': // feature enable if (leadchar == '?') { - if (n1 == 25) { - screen_cursor_enable(1); - } else if (n1 == 7) { - screen_wrap_enable(1); + if (n1 == 25) screen_cursor_enable(1); + else if (n1 == 7) screen_wrap_enable(1); + else if (n1 == 1) { + // TODO something with arrow keys?? + } + else { + ansi_warn("NOIMPL DEC opt %d", n1); + } + } + else { + if (n1 == 4) { + // TODO insert mode, i think this is to suppress user input + } + else { + ansi_warn("NOIMPL flag %d", n1); } } break; case 'l': // feature disable if (leadchar == '?') { - if (n1 == 25) { - screen_cursor_enable(0); - } else if (n1 == 7) { - screen_wrap_enable(0); + if (n1 == 25) screen_cursor_enable(0); + else if (n1 == 7) screen_wrap_enable(0); + else if (n1 == 1) { + // TODO see above + } + else { + ansi_warn("NOIMPL DEC opt %d", n1); + } + } + else { + if (n1 == 4) { + // TODO see above + } + else { + ansi_warn("NOIMPL flag %d", n1); } } break; case 'm': // SGR - graphics rendition aka attributes + if (count == 0) { + count = 1; // this makes it work as 0 (reset) + } + // iterate arguments - for (int i = 0; i < CSI_N_MAX; i++) { + for (int i = 0; i < count; i++) { int n = params[i]; - if (i == 0 && n == 0) { // reset SGR + if (n == 0) { // reset SGR screen_reset_cursor(); // resets colors, inverse and bold. - break; // cannot combine reset with others - discard } else if (n >= 30 && n <= 37) screen_set_fg((Color) (n - 30)); // ANSI normal fg else if (n >= 40 && n <= 47) screen_set_bg((Color) (n - 40)); // ANSI normal bg @@ -259,6 +312,9 @@ apars_handle_CSI(char leadchar, int *params, char keychar) else if (n == 21 || n == 22) screen_set_bold(false); // bold off else if (n >= 90 && n <= 97) screen_set_fg((Color) (n - 90 + 8)); // AIX bright fg else if (n >= 100 && n <= 107) screen_set_bg((Color) (n - 100 + 8)); // AIX bright bg + else { + ansi_warn("NOIMPL SGR attr %d", n); + } } break; @@ -281,6 +337,30 @@ apars_handle_CSI(char leadchar, int *params, char keychar) case 'P': screen_delete_characters(n1); break; + + case 'r': + // TODO scrolling region + ansi_warn("NOIMPL scrolling region"); + break; + + case 'g': + // TODO clear tab + ansi_warn("NOIMPL clear tab"); + break; + + case 'Z': + // TODO back tab + ansi_warn("NOIMPL cursor back tab"); + break; + + case 'q': + // TODO setmode (??) + ansi_warn("NOIMPL CSI setmode %d", n1); + break; + + default: + ansi_warn("Unknown CSI: %c", keychar); + apars_handle_badseq(); } } @@ -291,6 +371,9 @@ void ICACHE_FLASH_ATTR apars_handle_hashCode(char c) case '8': screen_fill_with_E(); break; + + default: + ansi_warn("Unknown # sequence: %c", c); } } @@ -301,22 +384,48 @@ void ICACHE_FLASH_ATTR apars_handle_shortCode(char c) case 'c': // screen reset screen_reset(); break; + case '7': // save cursor + attrs screen_cursor_save(true); break; + case '8': // restore cursor + attrs screen_cursor_restore(true); break; + case 'E': // same as CR LF screen_cursor_move(1, 0, false); screen_cursor_set_x(0); break; + case 'D': // move cursor down, scroll screen up if needed screen_cursor_move(1, 0, true); break; + case 'M': // move cursor up, scroll screen down if needed screen_cursor_move(-1, 0, true); break; + + case 'H': + // TODO set tab +// ansi_warn("NOIMPL set tab"); + break; + + // TODO those don't seem to do anything + case '>': +// ansi_warn("NOIMPL NUMKP"); + break; + + case '<': +// ansi_warn("NOIMPL SETANSI"); + break; + + case '=': +// ansi_warn("NOIMPL ALTKP"); + break; + + default: + ansi_warn("Unknown 1-char seq %c", c); } } diff --git a/user/ansi_parser_callbacks.h b/user/ansi_parser_callbacks.h index 49b5c84..c0101f1 100644 --- a/user/ansi_parser_callbacks.h +++ b/user/ansi_parser_callbacks.h @@ -1,13 +1,15 @@ #ifndef ANSI_PARSER_CALLBACKS_H #define ANSI_PARSER_CALLBACKS_H -void apars_handle_badseq(void); void apars_handle_plainchar(char c); -void apars_handle_CSI(char leadchar, int *params, char keychar); +void apars_handle_CSI(char leadchar, int *params, int count, char keychar); void apars_handle_OSC_SetScreenSize(int rows, int cols); void apars_handle_OSC_SetButton(int num, const char *buffer); void apars_handle_OSC_SetTitle(const char *buffer); void apars_handle_shortCode(char c); void apars_handle_hashCode(char c); +void apars_handle_characterSet(char leadchar, char c); +void apars_handle_setXCtrls(char c); +void apars_reset_utf8buffer(void); #endif //ESP_VT100_FIRMWARE_ANSI_PARSER_CALLBACKS_H diff --git a/user/cgi_sockets.c b/user/cgi_sockets.c index 1b9283d..b74a532 100644 --- a/user/cgi_sockets.c +++ b/user/cgi_sockets.c @@ -98,7 +98,7 @@ void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags) // TODO re-implement those, use single byte markers and B2 encoding - dbg("Sock RX str: %s, len %d", data, len); + ws_dbg("Sock RX str: %s, len %d", data, len); if (strstarts(data, "STR:")) { // pass string verbatim @@ -134,24 +134,24 @@ void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags) } if (!screen_isCoordValid(y, x)) { - warn("Mouse input at invalid coordinates"); + ws_warn("Mouse input at invalid coordinates"); return; } - dbg("Screen clicked at row %d, col %d", y+1, x+1); + ws_dbg("Screen clicked at row %d, col %d", y+1, x+1); // Send as 1-based to user sprintf(buf, "\033[%d;%dM", y+1, x+1); UART_WriteString(UART0, buf, UART_TIMEOUT_US); } else { - warn("Bad command."); + ws_warn("Bad command."); } } /** Socket connected for updates */ void ICACHE_FLASH_ATTR updateSockConnect(Websock *ws) { - info("Socket connected to "URL_WS_UPDATE); + ws_info("Socket connected to "URL_WS_UPDATE); ws->recvCb = updateSockRx; } diff --git a/user/cgi_sockets.h b/user/cgi_sockets.h index 8aebbc9..4b93046 100644 --- a/user/cgi_sockets.h +++ b/user/cgi_sockets.h @@ -9,4 +9,15 @@ void updateSockConnect(Websock *ws); void screen_notifyChange(ScreenNotifyChangeTopic topic); +// defined in the makefile +#if DEBUG_INPUT +#define ws_warn warn +#define ws_dbg dbg +#define ws_info info +#else +#define ws_warn(...) +#define ws_dbg(...) +#define ws_info(...) +#endif + #endif //CGI_SOCKETS_H diff --git a/user/screen.c b/user/screen.c index 9210684..74ebc00 100644 --- a/user/screen.c +++ b/user/screen.c @@ -186,12 +186,10 @@ screen_reset(void) void ICACHE_FLASH_ATTR screen_reset_cursor(void) { - NOTIFY_LOCK(); cursor.fg = termconf_scratch.default_fg; cursor.bg = termconf_scratch.default_bg; cursor.inverse = 0; cursor.bold = 0; - NOTIFY_DONE(); } /** @@ -712,7 +710,7 @@ screen_putchar(const char *ch) default: if (ch[0] < ' ') { // Discard - warn("Ignoring control char %d", (int)ch); + warn("Ignoring control char %d", (int)ch[0]); goto done; } }