diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c24afe..3855e8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,7 +124,7 @@ set(SOURCE_FILES user/persist.h include/helpers.h user/syscfg.c - user/syscfg.h user/ascii.h) + user/syscfg.h user/ascii.h user/sgr.h) include_directories(include) include_directories(user) diff --git a/Makefile b/Makefile index f0c38ec..6e1a9d1 100644 --- a/Makefile +++ b/Makefile @@ -73,7 +73,7 @@ CFLAGS += -DADMIN_PASSWORD=$(ADMIN_PASSWORD) # Debug logging CFLAGS += -DDEBUG_ANSI=1 -CFLAGS += -DDEBUG_INPUT=0 +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 28f421b..b677b50 100644 --- a/user/ansi_parser.c +++ b/user/ansi_parser.c @@ -13,43 +13,43 @@ 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, 1, - 15, 1, 16, 1, 17, 2, 2, 5, - 2, 10, 11, 2, 10, 12 + 11, 1, 12, 1, 13, 1, 14, 2, + 3, 6, 2, 8, 9 }; 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 + 0, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 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 = 10; static const int ansi_error = 0; static const int ansi_en_CSI_body = 5; -static const int ansi_en_OSC_body = 7; -static const int ansi_en_TITLE_body = 25; -static const int ansi_en_charsetcmd_body = 27; +static const int ansi_en_StrCmd_body = 7; +static const int ansi_en_charsetcmd_body = 9; static const int ansi_en_main = 1; /* #line 11 "user/ansi_parser.rl" */ +// Max nr of CSI parameters +#define CSI_N_MAX 10 +#define STR_CHAR_MAX 64 + static volatile int cs = -1; -static volatile bool inside_osc = false; +static volatile bool inside_string = false; +// public volatile u32 ansi_parser_char_cnt = 0; void ICACHE_FLASH_ATTR ansi_parser_reset(void) { if (cs != ansi_start) { cs = ansi_start; - inside_osc = false; + inside_string = false; apars_reset_utf8buffer(); ansi_warn("Parser timeout, state reset"); } @@ -100,13 +100,13 @@ void ICACHE_FLASH_ATTR ansi_parser(char newchar) { // 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]; - static int osc_bi; // buffer char index + static char leadchar; + static int arg_ni; + static int arg_cnt; + static int arg[CSI_N_MAX]; + static char csi_char; + static char string_buffer[STR_CHAR_MAX]; + static int str_ni; // This is used to detect timeout delay (time since last rx char) ansi_parser_char_cnt++; @@ -119,7 +119,7 @@ ansi_parser(char newchar) cs = ansi_start; } -/* #line 87 "user/ansi_parser.rl" */ +/* #line 92 "user/ansi_parser.rl" */ #if DEBUG_ANSI memset(history, 0, sizeof(history)); @@ -137,7 +137,7 @@ ansi_parser(char newchar) if (newchar < ' ') { switch (newchar) { case ESC: - if (!inside_osc) { + if (!inside_string) { // Reset state cs = ansi_start; // now the ESC will be processed by the parser @@ -168,7 +168,7 @@ ansi_parser(char newchar) case BEL: // bel is also used to terminate OSC - if (!inside_osc) { + if (!inside_string) { apars_handle_bel(); return; } @@ -224,21 +224,29 @@ case 2: switch( (*p) ) { case 32: goto tr3; case 35: goto tr4; - case 91: goto tr7; - case 93: goto tr8; - case 107: goto tr9; + case 37: goto tr5; + case 80: goto tr7; + case 88: goto tr7; + case 91: goto tr8; + case 107: goto tr7; } - if ( (*p) < 60 ) { - if ( (*p) > 47 ) { - if ( 48 <= (*p) && (*p) <= 57 ) + if ( (*p) < 64 ) { + if ( (*p) < 48 ) { + if ( 40 <= (*p) && (*p) <= 47 ) + goto tr5; + } else if ( (*p) > 57 ) { + if ( 60 <= (*p) && (*p) <= 62 ) goto tr6; - } else if ( (*p) >= 40 ) - goto tr5; - } else if ( (*p) > 62 ) { - if ( (*p) > 90 ) { - if ( 97 <= (*p) && (*p) <= 122 ) + } else + goto tr6; + } else if ( (*p) > 92 ) { + if ( (*p) < 97 ) { + if ( 93 <= (*p) && (*p) <= 95 ) + goto tr7; + } else if ( (*p) > 122 ) { + if ( 124 <= (*p) && (*p) <= 126 ) goto tr6; - } else if ( (*p) >= 65 ) + } else goto tr6; } else goto tr6; @@ -246,176 +254,89 @@ case 2: case 0: goto _out; case 3: - if ( 70 <= (*p) && (*p) <= 71 ) - goto tr10; + if ( (*p) > 71 ) { + if ( 76 <= (*p) && (*p) <= 78 ) + goto tr9; + } else if ( (*p) >= 70 ) + goto tr9; goto tr2; -case 28: +case 10: if ( (*p) == 27 ) goto tr1; goto tr0; case 4: if ( 48 <= (*p) && (*p) <= 57 ) - goto tr11; + goto tr10; goto tr2; case 5: switch( (*p) ) { - case 59: goto tr14; - case 64: goto tr15; + case 59: goto tr13; + case 64: goto tr14; } if ( (*p) < 60 ) { if ( (*p) > 47 ) { if ( 48 <= (*p) && (*p) <= 57 ) - goto tr13; + goto tr12; } else if ( (*p) >= 32 ) - goto tr12; + goto tr11; } else if ( (*p) > 63 ) { if ( (*p) > 90 ) { if ( 96 <= (*p) && (*p) <= 122 ) - goto tr16; + goto tr15; } else if ( (*p) >= 65 ) - goto tr16; + goto tr15; } else - goto tr12; + goto tr11; goto tr2; case 6: if ( (*p) == 59 ) - goto tr14; + goto tr13; if ( (*p) < 64 ) { if ( 48 <= (*p) && (*p) <= 57 ) - goto tr13; + goto tr12; } else if ( (*p) > 90 ) { if ( 96 <= (*p) && (*p) <= 122 ) - goto tr16; + goto tr15; } else - goto tr16; + goto tr15; goto tr2; -case 29: +case 11: goto tr2; -case 30: +case 12: if ( (*p) == 59 ) - goto tr14; + goto tr13; if ( (*p) < 64 ) { if ( 48 <= (*p) && (*p) <= 57 ) - goto tr13; + goto tr12; } else if ( (*p) > 90 ) { if ( 96 <= (*p) && (*p) <= 122 ) - goto tr16; + goto tr15; } else - goto tr16; + goto tr15; goto tr2; case 7: switch( (*p) ) { - case 48: goto tr17; - case 66: goto tr18; - case 84: goto tr19; - case 87: goto tr20; + case 7: goto tr17; + case 27: goto tr18; } - goto tr2; -case 8: - if ( (*p) == 59 ) - goto tr21; - goto tr2; -case 31: - goto tr2; -case 9: - if ( (*p) == 84 ) - goto tr22; - goto tr2; -case 10: - if ( (*p) == 78 ) - goto tr23; - goto tr2; -case 11: - if ( 48 <= (*p) && (*p) <= 57 ) - goto tr24; - goto tr2; -case 12: - if ( (*p) == 61 ) - goto tr25; - goto tr2; + goto tr16; case 13: switch( (*p) ) { - case 7: goto tr27; - case 27: goto tr28; - } - goto tr26; -case 32: - switch( (*p) ) { - case 7: goto tr27; - case 27: goto tr28; - } - goto tr26; -case 14: - if ( (*p) == 92 ) - goto tr29; - goto tr2; -case 15: - if ( (*p) == 73 ) - goto tr30; - goto tr2; -case 16: - if ( (*p) == 84 ) - goto tr31; - goto tr2; -case 17: - if ( (*p) == 76 ) - goto tr32; - goto tr2; -case 18: - if ( (*p) == 69 ) - goto tr33; - goto tr2; -case 19: - if ( (*p) == 61 ) - goto tr21; - goto tr2; -case 20: - if ( 48 <= (*p) && (*p) <= 57 ) - goto tr34; - goto tr2; -case 21: - if ( (*p) == 59 ) - goto tr35; - if ( 48 <= (*p) && (*p) <= 57 ) - goto tr34; - goto tr2; -case 22: - if ( 48 <= (*p) && (*p) <= 57 ) - goto tr36; - goto tr2; -case 23: - switch( (*p) ) { - case 7: goto tr37; - case 27: goto tr38; + case 7: goto tr17; + case 27: goto tr18; } - if ( 48 <= (*p) && (*p) <= 57 ) - goto tr36; - goto tr2; -case 24: - if ( (*p) == 92 ) - goto tr37; - goto tr2; -case 25: - switch( (*p) ) { - case 7: goto tr40; - case 27: goto tr41; - } - goto tr39; -case 33: - switch( (*p) ) { - case 7: goto tr40; - case 27: goto tr41; - } - goto tr39; -case 26: + goto tr16; +case 8: if ( (*p) == 92 ) - goto tr42; + goto tr19; goto tr2; -case 34: +case 14: goto tr2; -case 27: - goto tr43; -case 35: +case 9: + if ( (*p) == 27 ) + goto tr2; + goto tr20; +case 15: goto tr2; } @@ -424,232 +345,170 @@ case 35: tr1: cs = 2; goto _again; tr3: cs = 3; goto _again; tr4: cs = 4; goto _again; + tr11: cs = 6; goto f8; tr12: cs = 6; goto f9; tr13: cs = 6; goto f10; - tr14: cs = 6; goto f11; - tr17: cs = 8; goto _again; - tr18: cs = 9; goto _again; - tr22: cs = 10; goto _again; - tr23: cs = 11; goto _again; - tr24: cs = 12; goto f10; - tr25: cs = 13; goto _again; - tr26: cs = 13; goto f14; - tr28: cs = 14; goto _again; - tr19: cs = 15; goto _again; - tr30: cs = 16; goto _again; - tr31: cs = 17; goto _again; - tr32: cs = 18; goto _again; - tr33: cs = 19; goto _again; - tr20: cs = 20; goto _again; - tr34: cs = 21; goto f10; - tr35: cs = 22; goto f11; - tr36: cs = 23; goto f10; - tr38: cs = 24; goto _again; - tr39: cs = 25; goto f14; - tr41: cs = 26; goto _again; - tr5: cs = 28; goto f2; - tr6: cs = 28; goto f3; - tr7: cs = 28; goto f4; - tr8: cs = 28; goto f5; - tr9: cs = 28; goto f6; - tr10: cs = 28; goto f7; - tr11: cs = 28; goto f8; - tr16: cs = 29; goto f13; - tr15: cs = 30; goto f12; - tr21: cs = 31; goto f6; - tr29: cs = 31; goto f16; - tr37: cs = 31; goto f17; - tr27: cs = 32; goto f15; - tr40: cs = 33; goto f18; - tr42: cs = 34; goto f19; - tr43: cs = 35; goto f20; - - f1: _acts = _ansi_actions + 1; 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; - f5: _acts = _ansi_actions + 15; goto execFuncs; - f6: _acts = _ansi_actions + 17; goto execFuncs; - f17: _acts = _ansi_actions + 19; goto execFuncs; - f14: _acts = _ansi_actions + 21; goto execFuncs; - f19: _acts = _ansi_actions + 23; goto execFuncs; - f16: _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; - f18: _acts = _ansi_actions + 40; goto execFuncs; - f15: _acts = _ansi_actions + 43; goto execFuncs; + tr16: cs = 7; goto f13; + tr18: cs = 8; goto _again; + tr5: cs = 10; goto f2; + tr6: cs = 10; goto f3; + tr7: cs = 10; goto f4; + tr8: cs = 10; goto f5; + tr9: cs = 10; goto f6; + tr10: cs = 10; goto f7; + tr15: cs = 11; goto f12; + tr14: cs = 12; goto f11; + tr17: cs = 13; goto f14; + tr19: cs = 14; goto f15; + tr20: cs = 15; goto f16; + + f0: _acts = _ansi_actions + 1; goto execFuncs; + f1: _acts = _ansi_actions + 3; goto execFuncs; + f5: _acts = _ansi_actions + 5; goto execFuncs; + f8: _acts = _ansi_actions + 7; goto execFuncs; + f9: _acts = _ansi_actions + 9; goto execFuncs; + f10: _acts = _ansi_actions + 11; goto execFuncs; + f12: _acts = _ansi_actions + 13; goto execFuncs; + f4: _acts = _ansi_actions + 15; goto execFuncs; + f13: _acts = _ansi_actions + 17; goto execFuncs; + f15: _acts = _ansi_actions + 19; goto execFuncs; + f7: _acts = _ansi_actions + 21; goto execFuncs; + f3: _acts = _ansi_actions + 23; goto execFuncs; + f6: _acts = _ansi_actions + 25; goto execFuncs; + f2: _acts = _ansi_actions + 27; goto execFuncs; + f16: _acts = _ansi_actions + 29; goto execFuncs; + f11: _acts = _ansi_actions + 31; goto execFuncs; + f14: _acts = _ansi_actions + 34; goto execFuncs; execFuncs: _nacts = *_acts++; while ( _nacts-- > 0 ) { switch ( *_acts++ ) { case 0: -/* #line 180 "user/ansi_parser.rl" */ +/* #line 185 "user/ansi_parser.rl" */ + { + ansi_warn("Parser error."); + apars_handle_badseq(); + inside_string = false; // no longer in string, for sure + {cs = 1;goto _again;} + } + break; + case 1: +/* #line 194 "user/ansi_parser.rl" */ { if ((*p) != 0) { apars_handle_plainchar((*p)); } } break; - case 1: -/* #line 189 "user/ansi_parser.rl" */ + case 2: +/* #line 202 "user/ansi_parser.rl" */ { // Reset the CSI builder - csi_leading = csi_char = 0; - csi_ni = 0; - csi_cnt = 0; + leadchar = 0; + arg_ni = 0; + arg_cnt = 0; // Zero out digits for(int i = 0; i < CSI_N_MAX; i++) { - csi_n[i] = 0; + arg[i] = 0; } {cs = 5;goto _again;} } break; - case 2: -/* #line 203 "user/ansi_parser.rl" */ - { - csi_leading = (*p); - } - break; case 3: -/* #line 207 "user/ansi_parser.rl" */ +/* #line 216 "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'); - } + leadchar = (*p); } break; case 4: -/* #line 215 "user/ansi_parser.rl" */ +/* #line 220 "user/ansi_parser.rl" */ { - if (csi_cnt == 0) csi_cnt = 1; // handle case when first arg is empty - csi_cnt++; - csi_ni++; + if (arg_cnt == 0) arg_cnt = 1; + // x10 + digit + if (arg_ni < CSI_N_MAX) { + arg[arg_ni] = arg[arg_ni]*10 + ((*p) - '0'); + } } break; case 5: -/* #line 221 "user/ansi_parser.rl" */ +/* #line 228 "user/ansi_parser.rl" */ { - csi_char = (*p); - apars_handle_CSI(csi_leading, csi_n, csi_cnt, csi_char); - {cs = 1;goto _again;} + if (arg_cnt == 0) arg_cnt = 1; // handle case when first arg is empty + arg_cnt++; + arg_ni++; } break; case 6: -/* #line 227 "user/ansi_parser.rl" */ +/* #line 234 "user/ansi_parser.rl" */ { - ansi_warn("Parser error."); - apars_handle_badseq(); + apars_handle_CSI(leadchar, arg, arg_cnt, (*p)); {cs = 1;goto _again;} } break; case 7: /* #line 245 "user/ansi_parser.rl" */ { - csi_ni = 0; - - // we reuse the CSI numeric buffer - for(int i = 0; i < CSI_N_MAX; i++) { - csi_n[i] = 0; - } - - osc_bi = 0; - osc_buffer[0] = '\0'; - - inside_osc = true; - + leadchar = (*p); + str_ni = 0; + string_buffer[0] = '\0'; + inside_string = true; {cs = 7;goto _again;} } break; case 8: -/* #line 262 "user/ansi_parser.rl" */ +/* #line 253 "user/ansi_parser.rl" */ { - osc_bi = 0; - osc_buffer[0] = '\0'; - inside_osc = true; - {cs = 25;goto _again;} + string_buffer[str_ni++] = (*p); } break; case 9: -/* #line 269 "user/ansi_parser.rl" */ +/* #line 257 "user/ansi_parser.rl" */ { - apars_handle_OSC_SetScreenSize(csi_n[0], csi_n[1]); - inside_osc = false; + inside_string = false; + string_buffer[str_ni++] = '\0'; + apars_handle_StrCmd(leadchar, string_buffer); {cs = 1;goto _again;} } break; case 10: -/* #line 275 "user/ansi_parser.rl" */ +/* #line 270 "user/ansi_parser.rl" */ { - osc_buffer[osc_bi++] = (*p); + apars_handle_hashCode((*p)); + {cs = 1;goto _again;} } break; case 11: -/* #line 279 "user/ansi_parser.rl" */ +/* #line 275 "user/ansi_parser.rl" */ { - osc_buffer[osc_bi++] = '\0'; - apars_handle_OSC_SetTitle(osc_buffer); - inside_osc = false; + apars_handle_shortCode((*p)); {cs = 1;goto _again;} } break; case 12: -/* #line 286 "user/ansi_parser.rl" */ +/* #line 280 "user/ansi_parser.rl" */ { - osc_buffer[osc_bi++] = '\0'; - apars_handle_OSC_SetButton(csi_n[0], osc_buffer); - inside_osc = false; + apars_handle_spaceCmd((*p)); {cs = 1;goto _again;} } break; case 13: -/* #line 319 "user/ansi_parser.rl" */ +/* #line 287 "user/ansi_parser.rl" */ { - apars_handle_hashCode((*p)); - {cs = 1;goto _again;} + leadchar = (*p); + {cs = 9;goto _again;} } break; case 14: -/* #line 324 "user/ansi_parser.rl" */ - { - apars_handle_shortCode((*p)); - {cs = 1;goto _again;} - } - break; - case 15: -/* #line 329 "user/ansi_parser.rl" */ - { - apars_handle_setXCtrls((*p)); // weird control settings like 7 bit / 8 bit mode - {cs = 1;goto _again;} - } - break; - case 16: -/* #line 334 "user/ansi_parser.rl" */ - { - // abuse the buffer for storing the leading char - osc_buffer[0] = (*p); - {cs = 27;goto _again;} - } - break; - case 17: -/* #line 340 "user/ansi_parser.rl" */ +/* #line 292 "user/ansi_parser.rl" */ { - apars_handle_characterSet(osc_buffer[0], (*p)); + apars_handle_characterSet(leadchar, (*p)); {cs = 1;goto _again;} } break; -/* #line 653 "user/ansi_parser.c" */ +/* #line 512 "user/ansi_parser.c" */ } } goto _again; @@ -666,17 +525,18 @@ _again: unsigned int __nacts = (unsigned int) *__acts++; while ( __nacts-- > 0 ) { switch ( *__acts++ ) { - case 6: -/* #line 227 "user/ansi_parser.rl" */ + case 0: +/* #line 185 "user/ansi_parser.rl" */ { ansi_warn("Parser error."); apars_handle_badseq(); + inside_string = false; // no longer in string, for sure {cs = 1; if ( p == pe ) goto _test_eof; goto _again;} } break; -/* #line 680 "user/ansi_parser.c" */ +/* #line 540 "user/ansi_parser.c" */ } } } @@ -684,8 +544,6 @@ goto _again;} _out: {} } -/* #line 364 "user/ansi_parser.rl" */ +/* #line 315 "user/ansi_parser.rl" */ } - -// 'ESC k blah OSC_end' is a shortcut for setting title (k is defined in GNU screen as Title Definition String) diff --git a/user/ansi_parser.h b/user/ansi_parser.h index c922986..72a2bee 100644 --- a/user/ansi_parser.h +++ b/user/ansi_parser.h @@ -4,19 +4,13 @@ #include #include -// Max nr of CSI parameters -#define CSI_N_MAX 10 -#define OSC_CHAR_MAX TERM_TITLE_LEN - extern void apars_handle_plainchar(char c); -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_CSI(char leadchar, const int *params, int count, char keychar); +extern void apars_handle_StrCmd(char leadchar, 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_handle_spaceCmd(char c); extern void apars_reset_utf8buffer(void); extern void apars_handle_bel(void); diff --git a/user/ansi_parser.rl b/user/ansi_parser.rl index 321deba..6d1654b 100644 --- a/user/ansi_parser.rl +++ b/user/ansi_parser.rl @@ -10,16 +10,21 @@ write data; }%% +// Max nr of CSI parameters +#define CSI_N_MAX 10 +#define STR_CHAR_MAX 64 + static volatile int cs = -1; -static volatile bool inside_osc = false; +static volatile bool inside_string = false; +// public volatile u32 ansi_parser_char_cnt = 0; void ICACHE_FLASH_ATTR ansi_parser_reset(void) { if (cs != ansi_start) { cs = ansi_start; - inside_osc = false; + inside_string = false; apars_reset_utf8buffer(); ansi_warn("Parser timeout, state reset"); } @@ -70,13 +75,13 @@ void ICACHE_FLASH_ATTR ansi_parser(char newchar) { // 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]; - static int osc_bi; // buffer char index + static char leadchar; + static int arg_ni; + static int arg_cnt; + static int arg[CSI_N_MAX]; + static char csi_char; + static char string_buffer[STR_CHAR_MAX]; + static int str_ni; // This is used to detect timeout delay (time since last rx char) ansi_parser_char_cnt++; @@ -101,7 +106,7 @@ ansi_parser(char newchar) if (newchar < ' ') { switch (newchar) { case ESC: - if (!inside_osc) { + if (!inside_string) { // Reset state cs = ansi_start; // now the ESC will be processed by the parser @@ -132,7 +137,7 @@ ansi_parser(char newchar) case BEL: // bel is also used to terminate OSC - if (!inside_osc) { + if (!inside_string) { apars_handle_bel(); return; } @@ -173,7 +178,16 @@ ansi_parser(char newchar) ESC = 27; NOESC = (any - ESC); TOK_ST = ESC '\\'; # String terminator - used for OSC commands - OSC_END = ('\a' | TOK_ST); + STR_END = ('\a' | TOK_ST); + + # --- Error handler --- + + action errBadSeq { + ansi_warn("Parser error."); + apars_handle_badseq(); + inside_string = false; // no longer in string, for sure + fgoto main; + } # --- Regular characters to be printed --- @@ -183,54 +197,42 @@ ansi_parser(char newchar) } } - # --- CSI CSI commands (Select Graphic Rendition) --- - # Text color & style + # --- CSI commands --- action CSI_start { // Reset the CSI builder - csi_leading = csi_char = 0; - csi_ni = 0; - csi_cnt = 0; + leadchar = 0; + arg_ni = 0; + arg_cnt = 0; // Zero out digits for(int i = 0; i < CSI_N_MAX; i++) { - csi_n[i] = 0; + arg[i] = 0; } fgoto CSI_body; } action CSI_leading { - csi_leading = fc; + leadchar = fc; } action CSI_digit { - if (csi_cnt == 0) csi_cnt = 1; + if (arg_cnt == 0) arg_cnt = 1; // x10 + digit - if (csi_ni < CSI_N_MAX) { - csi_n[csi_ni] = csi_n[csi_ni]*10 + (fc - '0'); + if (arg_ni < CSI_N_MAX) { + arg[arg_ni] = arg[arg_ni]*10 + (fc - '0'); } } action CSI_semi { - if (csi_cnt == 0) csi_cnt = 1; // handle case when first arg is empty - csi_cnt++; - csi_ni++; + if (arg_cnt == 0) arg_cnt = 1; // handle case when first arg is empty + arg_cnt++; + arg_ni++; } action CSI_end { - csi_char = fc; - apars_handle_CSI(csi_leading, csi_n, csi_cnt, csi_char); - fgoto main; - } - - action errBadSeq { - ansi_warn("Parser error."); - apars_handle_badseq(); - fgoto main; - } - - action back2main { + apars_handle_CSI(leadchar, arg, arg_cnt, fc); fgoto main; } @@ -238,83 +240,32 @@ ansi_parser(char newchar) ((digit @CSI_digit)* ';' @CSI_semi)* (digit @CSI_digit)* (alpha|'`'|'@') @CSI_end $!errBadSeq; + # --- String commands --- - # --- OSC commands (Operating System Commands) --- - # Module parametrisation - - action OSC_start { - csi_ni = 0; - - // we reuse the CSI numeric buffer - for(int i = 0; i < CSI_N_MAX; i++) { - csi_n[i] = 0; - } - - osc_bi = 0; - osc_buffer[0] = '\0'; - - inside_osc = true; - - fgoto OSC_body; + action StrCmd_start { + leadchar = fc; + str_ni = 0; + string_buffer[0] = '\0'; + inside_string = true; + fgoto StrCmd_body; } - # collecting title string; this can also be entered by ESC k - action SetTitle_start { - osc_bi = 0; - osc_buffer[0] = '\0'; - inside_osc = true; - fgoto TITLE_body; + action StrCmd_char { + string_buffer[str_ni++] = fc; } - action OSC_resize { - apars_handle_OSC_SetScreenSize(csi_n[0], csi_n[1]); - inside_osc = false; + action StrCmd_end { + inside_string = false; + string_buffer[str_ni++] = '\0'; + apars_handle_StrCmd(leadchar, string_buffer); fgoto main; } - action OSC_text_char { - osc_buffer[osc_bi++] = fc; - } + # According to the spec, ESC should be allowed inside the string sequence. + # We disallow ESC for simplicity, as it's hardly ever used. + StrCmd_body := ((NOESC @StrCmd_char)* STR_END @StrCmd_end) $!errBadSeq; - action OSC_title { - osc_buffer[osc_bi++] = '\0'; - apars_handle_OSC_SetTitle(osc_buffer); - inside_osc = false; - fgoto main; - } - - action OSC_button { - osc_buffer[osc_bi++] = '\0'; - apars_handle_OSC_SetButton(csi_n[0], osc_buffer); - inside_osc = false; - fgoto main; - } - - # 0; is xterm title hack - OSC_body := ( - ("BTN" digit @CSI_digit '=' (NOESC @OSC_text_char)* OSC_END @OSC_button) | - ("TITLE=" @SetTitle_start) | - ("0;" @SetTitle_start) | - ('W' (digit @CSI_digit)+ ';' @CSI_semi (digit @CSI_digit)+ OSC_END @OSC_resize) - ) $!errBadSeq; - - TITLE_body := (NOESC @OSC_text_char)* OSC_END @OSC_title $!errBadSeq; - - action RESET_cmd { - // Reset screen - apars_handle_RESET_cmd(); - fgoto main; - } - - action CSI_SaveCursorAttrs { - apars_handle_saveCursorAttrs(); - fgoto main; - } - - action CSI_RestoreCursorAttrs { - apars_handle_restoreCursorAttrs(); - fgoto main; - } + # --- Single character ESC --- action HASH_code { apars_handle_hashCode(fc); @@ -326,23 +277,24 @@ ansi_parser(char newchar) fgoto main; } - action SetXCtrls { - apars_handle_setXCtrls(fc); // weird control settings like 7 bit / 8 bit mode + action SPACE_cmd { + apars_handle_spaceCmd(fc); fgoto main; } + # --- Charset selection --- + action CharsetCmd_start { - // abuse the buffer for storing the leading char - osc_buffer[0] = fc; + leadchar = fc; fgoto charsetcmd_body; } action CharsetCmd_end { - apars_handle_characterSet(osc_buffer[0], fc); + apars_handle_characterSet(leadchar, fc); fgoto main; } - charsetcmd_body := (any @CharsetCmd_end) $!errBadSeq; + charsetcmd_body := (NOESC @CharsetCmd_end) $!errBadSeq; # --- Main parser loop --- @@ -350,12 +302,11 @@ ansi_parser(char newchar) ( (NOESC @plain_char)* ESC ( ('[' @CSI_start) | - (']' @OSC_start) | + ([_\]Pk\^X] @StrCmd_start) | ('#' digit @HASH_code) | - ('k' @SetTitle_start) | - ([a-jl-zA-Z0-9=<>] @SHORT_code) | - ([()*+-./] @CharsetCmd_start) | - (' ' [FG] @SetXCtrls) + (([a-zA-Z0-9=<>~}|@\\] - [PXk]) @SHORT_code) | + ([()*+-./%] @CharsetCmd_start) | + (' ' [FGLMN] @SPACE_cmd) ) )+ $!errBadSeq; @@ -363,5 +314,3 @@ ansi_parser(char newchar) #*/ }%% } - -// 'ESC k blah OSC_end' is a shortcut for setting title (k is defined in GNU screen as Title Definition String) diff --git a/user/ansi_parser_callbacks.c b/user/ansi_parser_callbacks.c index d12f308..e1b19ae 100644 --- a/user/ansi_parser_callbacks.c +++ b/user/ansi_parser_callbacks.c @@ -9,8 +9,7 @@ #include "screen.h" #include "ansi_parser.h" #include "uart_driver.h" - -// screen manpage - https://www.gnu.org/software/screen/manual/html_node/Control-Sequences.html +#include "sgr.h" static char utf_collect[4]; static int utf_i = 0; @@ -18,6 +17,7 @@ static int utf_j = 0; /** * Handle a received plain character + * @param c - received character */ void ICACHE_FLASH_ATTR apars_handle_plainchar(char c) @@ -68,11 +68,15 @@ apars_handle_plainchar(char c) } return; -fail: - ansi_warn("Bad UTF-8: %0Xh", c); + fail: + ansi_warn("Bad UTF-8: %0Xh", c); apars_reset_utf8buffer(); } +/** + * Clear the buffer where we collect pieces of a code point. + * This is used for parser reset. + */ void ICACHE_FLASH_ATTR apars_reset_utf8buffer(void) { @@ -81,36 +85,60 @@ apars_reset_utf8buffer(void) memset(utf_collect, 0, 4); } +/** + * Send a response to UART0 + * @param str + */ +static void ICACHE_FLASH_ATTR +respond(const char *str) +{ + UART_WriteString(UART0, str, UART_TIMEOUT_US); +} + +/** + * Command to assign G0 or G1 + * @param leadchar - ( or ) for G0 or G1 + * @param c - character table ID (0, B etc) + */ void ICACHE_FLASH_ATTR apars_handle_characterSet(char leadchar, char c) { if (leadchar == '(') screen_set_charset(0, c); else if (leadchar == ')') screen_set_charset(1, c); + else { + ansi_warn("NOIMPL: ESC %c %c", leadchar, c); + } // other alternatives * + . - / not implemented } -/** ESC SP */ +/** + * ESC SP (this sets 8/7-bit mode and some other archaic options) + * @param c - key character + */ void ICACHE_FLASH_ATTR -apars_handle_setXCtrls(char c) +apars_handle_spaceCmd(char c) { - // this does not seem to do anything, sent by some unix programs -// ansi_warn("NOIMPL Select %cbit ctrls", c=='F'? '7':'8'); + ansi_warn("NOIMPL: ESC SP %c", c); } +/** + * Beep at the user. + */ void ICACHE_FLASH_ATTR apars_handle_bel(void) { + ansi_warn("NOIMPL: BEEP"); // TODO pass to the browser somehow } /** - * \brief Handle fully received CSI ANSI sequence - * \param leadchar - private range leading character, 0 if none - * \param params - array of CSI_N_MAX ints holding the numeric arguments - * \param keychar - the char terminating the sequence + * Handle fully received CSI ANSI sequence + * @param leadchar - private range leading character, 0 if none + * @param params - array of CSI_N_MAX ints holding the numeric arguments + * @param keychar - the char terminating the sequence */ void ICACHE_FLASH_ATTR -apars_handle_CSI(char leadchar, int *params, int count, char keychar) +apars_handle_CSI(char leadchar, const int *params, int count, char keychar) { int n1 = params[0]; int n2 = params[1]; @@ -151,25 +179,29 @@ apars_handle_CSI(char leadchar, int *params, int count, char keychar) case 'K': if (n1 > 2) n1 = 0; break; + + default: + // leave as is + break; } switch (keychar) { // CUU CUD CUF CUB case 'a': - case 'A': + case 'A': // Up screen_cursor_move(-n1, 0, false); break; case 'e': - case 'B': + case 'B': // Down screen_cursor_move(n1, 0, false); break; - case 'C': + case 'C': // Right (forward) screen_cursor_move(0, n1, false); break; - case 'D': + case 'D': // Left (backward) screen_cursor_move(0, -n1, false); break; @@ -194,7 +226,7 @@ apars_handle_CSI(char leadchar, int *params, int count, char keychar) screen_cursor_set_y(n1 - 1); break; // 1-based - // clear in line + // Clear in line case 'X': screen_clear_in_line(n1); break; // 1-based @@ -209,7 +241,7 @@ apars_handle_CSI(char leadchar, int *params, int count, char keychar) screen_cursor_set(n1-1, n2-1); break; // 1-based - case 'J': // ED - clear screen + case 'J': // Erase screen if (n1 == 0) { screen_clear(CLEAR_FROM_CURSOR); } else if (n1 == 1) { @@ -220,7 +252,7 @@ apars_handle_CSI(char leadchar, int *params, int count, char keychar) } break; - case 'K': // EL - clear line + case 'K': // Erase lines if (n1 == 0) { screen_clear_line(CLEAR_FROM_CURSOR); } else if (n1 == 1) { @@ -234,28 +266,26 @@ apars_handle_CSI(char leadchar, int *params, int count, char keychar) case 's': screen_cursor_save(0); break; case 'u': screen_cursor_restore(0); break; - case 'n': // queries + case 'n': // Queries if (n1 == 6) { // Query cursor position int x, y; screen_cursor_get(&y, &x); sprintf(buf, "\033[%d;%dR", y+1, x+1); - UART_WriteString(UART0, buf, UART_TIMEOUT_US); + respond(buf); } else if (n1 == 5) { // Query device status - reply "Device is OK" - UART_WriteString(UART0, "\033[0n", UART_TIMEOUT_US); + respond("\033[0n"); } else { - ansi_warn("NOIMPL query %d", n1); + ansi_warn("NOIMPL: CSI %d n", n1); } break; - // DECTCEM feature enable / disable - - case 'h': // feature enable + case 'h': // DEC feature enable yn = 1; - case 'l': // feature disable + case 'l': // DEC feature disable for (int i = 0; i < count; i++) { int n = params[i]; if (leadchar == '?') { @@ -269,19 +299,36 @@ apars_handle_CSI(char leadchar, int *params, int count, char keychar) screen_wrap_enable(yn); } else if (n == 8) { - // TODO autorepeat mode + // Key auto-repeat + // We don't implement this currently, but it could be added + // - discard repeated keypress events between keydown and keyup. + } + else if (n == 9 || (n >= 1000 && n <= 1006)) { + // TODO mouse + // 1000 - C11 mouse - Send Mouse X & Y on button press and release. + // 1001 - Hilite mouse tracking + // 1002 - Cell Motion Mouse Tracking + // 1003 - All Motion Mouse Tracking + // 1004 - Send FocusIn/FocusOut events + // 1005 - Enable UTF-8 Mouse Mode + // 1006 - SGR mouse mode + } + else if (n == 12) { + // TODO Cursor blink on/off } - else if (n == 9) { - // TODO X10 mouse + else if (n == 1049) { + // XTERM: optionally switch to/from alternate screen + // We can't implement this because of limited RAM size } - else if (n == 1000) { - // TODo X11 mouse + else if (n == 2004) { + // Bracketed paste mode + // Discard, we don't implement this } else if (n == 25) { screen_cursor_visible(yn); } else { -// ansi_warn("NOIMPL DEC opt %d", n); + ansi_warn("NOIMPL: CSI ? %d %c", n, keychar); } } else { @@ -292,13 +339,13 @@ apars_handle_CSI(char leadchar, int *params, int count, char keychar) screen_set_newline_mode(yn); } else { -// ansi_warn("NOIMPL flag %d", n); + ansi_warn("NOIMPL: CSI %d %c", n, keychar); } } } break; - case 'm': // SGR - graphics rendition aka attributes + case 'm': // SGR - set graphics rendition if (count == 0) { count = 1; // this makes it work as 0 (reset) } @@ -307,84 +354,85 @@ apars_handle_CSI(char leadchar, int *params, int count, char keychar) for (int i = 0; i < count; i++) { int n = params[i]; - if (n == 0) { // reset SGR - screen_reset_sgr(); // resets colors, inverse and bold. - } - 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 - else if (n == 39) screen_set_fg(termconf_scratch.default_fg); // default fg - else if (n == 49) screen_set_bg(termconf_scratch.default_bg); // default bg - - else if (n == 1) screen_attr_enable(ATTR_BOLD); - else if (n == 2) screen_attr_enable(ATTR_FAINT); - else if (n == 3) screen_attr_enable(ATTR_ITALIC); - else if (n == 4) screen_attr_enable(ATTR_UNDERLINE); - else if (n == 5 || n == 6) screen_attr_enable(ATTR_BLINK); // 6 - rapid blink, not supported - else if (n == 7) screen_inverse_enable(true); - else if (n == 9) screen_attr_enable(ATTR_STRIKE); - - else if (n == 20) screen_attr_enable(ATTR_FRAKTUR); - else if (n == 21) screen_attr_disable(ATTR_BOLD); - else if (n == 22) screen_attr_disable(ATTR_FAINT); - else if (n == 23) screen_attr_disable(ATTR_ITALIC|ATTR_FRAKTUR); - else if (n == 24) screen_attr_disable(ATTR_UNDERLINE); - else if (n == 25) screen_attr_disable(ATTR_BLINK); - else if (n == 27) screen_inverse_enable(false); - else if (n == 29) screen_attr_disable(ATTR_STRIKE); - - 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 + if (n == SGR_RESET) screen_reset_sgr(); + // -- set color -- + else if (n >= SGR_FG_START && n <= SGR_FG_END) screen_set_fg((Color) (n - SGR_FG_START)); // ANSI normal fg + else if (n >= SGR_BG_START && n <= SGR_BG_END) screen_set_bg((Color) (n - SGR_BG_START)); // ANSI normal bg + else if (n == SGR_FG_DEFAULT) screen_set_fg(termconf_scratch.default_fg); // default fg + else if (n == SGR_BG_DEFAULT) screen_set_bg(termconf_scratch.default_bg); // default bg + // -- set attr -- + else if (n == SGR_BOLD) screen_attr_enable(ATTR_BOLD); + else if (n == SGR_FAINT) screen_attr_enable(ATTR_FAINT); + else if (n == SGR_ITALIC) screen_attr_enable(ATTR_ITALIC); + else if (n == SGR_UNDERLINE) screen_attr_enable(ATTR_UNDERLINE); + else if (n == SGR_BLINK || n == SGR_BLINK_FAST) screen_attr_enable(ATTR_BLINK); // 6 - rapid blink, not supported + else if (n == SGR_INVERSE) screen_inverse_enable(true); + else if (n == SGR_STRIKE) screen_attr_enable(ATTR_STRIKE); + else if (n == SGR_FRAKTUR) screen_attr_enable(ATTR_FRAKTUR); + // -- clear attr -- + else if (n == SGR_OFF(SGR_BOLD)) screen_attr_disable(ATTR_BOLD); + else if (n == SGR_OFF(SGR_FAINT)) screen_attr_disable(ATTR_FAINT); + else if (n == SGR_OFF(SGR_ITALIC)) screen_attr_disable(ATTR_ITALIC|ATTR_FRAKTUR); + else if (n == SGR_OFF(SGR_UNDERLINE)) screen_attr_disable(ATTR_UNDERLINE); + else if (n == SGR_OFF(SGR_BLINK)) screen_attr_disable(ATTR_BLINK); + else if (n == SGR_OFF(SGR_INVERSE)) screen_inverse_enable(false); + else if (n == SGR_OFF(SGR_STRIKE)) screen_attr_disable(ATTR_STRIKE); + // -- AIX bright colors -- + else if (n >= SGR_FG_BRT_START && n <= SGR_FG_BRT_END) screen_set_fg((Color) ((n - SGR_FG_BRT_START) + 8)); // AIX bright fg + else if (n >= SGR_BG_BRT_START && n <= SGR_BG_BRT_END) screen_set_bg((Color) ((n - SGR_BG_BRT_START) + 8)); // AIX bright bg else { - ansi_warn("NOIMPL SGR attr %d", n); + ansi_warn("NOIMPL: SGR %d", n); } } break; - case 't': // xterm hacks + case 't': // xterm window hacks switch(n1) { case 8: // set size screen_resize(n2, n3); break; case 18: // report size printf(buf, "\033[8;%d;%dt", termconf_scratch.height, termconf_scratch.width); - UART_WriteString(UART0, buf, UART_TIMEOUT_US); + respond(buf); break; case 11: // Report iconified -> is not iconified - UART_WriteString(UART0, "\033[1t", UART_TIMEOUT_US); + respond("\033[1t"); break; case 21: // Report title - UART_WriteString(UART0, "\033]L", UART_TIMEOUT_US); - UART_WriteString(UART0, termconf_scratch.title, UART_TIMEOUT_US); - UART_WriteString(UART0, "\033\\", UART_TIMEOUT_US); + respond("\033]L"); + respond(termconf_scratch.title); + respond("\033\\"); break; case 24: // Set Height only screen_resize(n2, termconf_scratch.width); break; + default: + ansi_warn("NOIMPL CSI %d t", n1); + break; } break; - case 'L': + case 'L': // Insert lines (shove down) screen_insert_lines(n1); break; - case 'M': + case 'M': // Delete lines (pull up) screen_delete_lines(n1); break; - case '@': + case '@': // Insert in line (shove right) screen_insert_characters(n1); break; - case 'P': + case 'P': // Delete in line (pull left) screen_delete_characters(n1); break; case 'r': // TODO scrolling region -// ansi_warn("NOIMPL scrolling region"); break; - case 'g': + case 'g': // Clear tabs if (n1 == 3) { screen_clear_all_tabs(); } else { @@ -392,30 +440,32 @@ apars_handle_CSI(char leadchar, int *params, int count, char keychar) } break; - case 'Z': + case 'Z': // Tab backward for(; n1 > 0; n1--) { screen_tab_reverse(); } break; - case 'I': + case 'I': // Tab forward for(; n1 > 0; n1--) { screen_tab_forward(); } break; - case 'c': // CSI-c - // report capabilities (pretend we're vt4xx) - UART_WriteString(UART0, "\033[?64;22;c", UART_TIMEOUT_US); + case 'c': // CSI-c - report capabilities + respond("\033[?64;22;c"); // pretend we're vt400 break; default: - ansi_warn("Unknown CSI: %c", keychar); + ansi_warn("NOIMPL: CSI Pm %c", keychar); apars_handle_badseq(); } } -/** codes in the format ESC # n */ +/** + * Codes in the format ESC # n + * @param c - the trailing symbol (numeric ASCII) + */ void ICACHE_FLASH_ATTR apars_handle_hashCode(char c) { switch(c) { @@ -424,11 +474,14 @@ void ICACHE_FLASH_ATTR apars_handle_hashCode(char c) break; default: - ansi_warn("Bad seq: ESC # %c", c); + ansi_warn("NOIMPL: ESC # %c", c); } } -/** those are single-character escape codes (ESC x) */ +/** + * Single-character escape codes (ESC x) + * @param c - the trailing symbol (ASCII) + */ void ICACHE_FLASH_ATTR apars_handle_shortCode(char c) { switch(c) { @@ -469,43 +522,169 @@ void ICACHE_FLASH_ATTR apars_handle_shortCode(char c) screen_set_numpad_alt_mode(false); break; - case '<': - // "Enter ANSI / VT100 mode" - no-op + case '<': // "Enter ANSI / VT100 mode" - no-op (we don't support VT52 mode) break; case '=': screen_set_numpad_alt_mode(true); break; + case '|': // Invoke the G3 Character Set as GR (LS3R). + case '}': // Invoke the G2 Character Set as GR (LS2R). + case '~': // Invoke the G1 Character Set as GR (LS1R). + // Those do not seem to do anything TODO investigate + break; + + case '@': // no-op padding char (?) + break; + + case '\\': // spurious string terminator + break; + default: - ansi_warn("Bad seq: ESC %c", c); + ansi_warn("NOIMPL: ESC %c", c); } } /** - * Handle a screen resize request + * Helper function to set terminal title + * @param str - title text */ -void ICACHE_FLASH_ATTR -apars_handle_OSC_SetScreenSize(int rows, int cols) +static void ICACHE_FLASH_ATTR +set_title(const char *str) { -// info("OSC: Set screen size to %d x %d", rows, cols); + strncpy(termconf_scratch.title, str, TERM_TITLE_LEN); + screen_notifyChange(CHANGE_LABELS); +} - screen_resize(rows, cols); +/** + * Helper function to set terminal button label + * @param num - button number 1-5 + * @param str - button text + */ +static void ICACHE_FLASH_ATTR +set_button_text(int num, const char *str) +{ + strncpy(termconf_scratch.btn[num-1], str, TERM_BTN_LEN); + screen_notifyChange(CHANGE_LABELS); } +/** + * Helper function to parse incoming OSC (Operating System Control) + * @param buffer - the OSC body (after OSC and before ST) + */ +static void ICACHE_FLASH_ATTR +parse_osc(const char *buffer) +{ + const char *orig_buff = buffer; + int n = 0; + char c = 0; + while ((c = *buffer++) != 0) { + if (c >= '0' && c <= '9') { + n = (n * 10 + (c - '0')); + } else { + break; + } + } + + if (c == ';') { + // Do something with the data string and number + // (based on xterm manpage) + if (n == 0 || n == 2) set_title(buffer); + else if (n >= 81 && n <= 85) { // numbers chosen to not collide with any xterm supported codes + set_button_text(n - 80, buffer); + } + else { + ansi_warn("NOIMPL: OSC %d ; %s ST", n, buffer); + } + } + else { + ansi_warn("BAD OSC: %s", orig_buff); + } +} -void ICACHE_FLASH_ATTR -apars_handle_OSC_SetButton(int num, const char *buffer) +/** + * Helper function to parse incoming DCS (Device Control String) + * @param buffer - the DCS body (after DCS and before ST) + */ +static void ICACHE_FLASH_ATTR +parse_dcs(const char *buffer) { - strncpy(termconf_scratch.btn[num-1], buffer, TERM_BTN_LEN); -// info("OSC: Set BTN%d = %s", num, buffer); - screen_notifyChange(CHANGE_LABELS); + char buf[64]; // just about big enough for full-house SGR + size_t len = strlen(buffer); + if ((len == 3 || len == 4) && strneq(buffer, "$q", 2)) { + // DECRQSS - Request Status String + if (strneq(buffer+2, "\"p", 2)) { + // DECSCL - Select Conformance Level + respond("\033[P1$r64;1\"p\033\\"); // 64;1 - Pretend we are VT400 with 7-bit characters + } + else if (strneq(buffer+2, "\"q", 2)) { + // DECSCA - Select character protection attribute + sprintf(buf, "\033[P1$r%d\"q\033\\", 0); // 0 - Can erase - TODO real protection status if implemented + respond(buf); + } + else if (buffer[2] == 'r') { + // DECSTBM - Query scrolling region + sprintf(buf, "\033[P1$r%d;%dr\033\\", 1, termconf_scratch.height); // 1-80 TODO real extent of scrolling region + respond(buf); + } + else if (buffer[2] == 's') { + // DECSLRM - Query horizontal margins + sprintf(buf, "\033[P1$r%d;%ds\033\\", 1, termconf_scratch.width); // Can erase - TODO real extent of horiz margins if implemented + respond(buf); + } + else if (buffer[2] == 'm') { + // SGR - query SGR + respond("\033[P1$r"); + screen_report_sgr(buf); + respond(buf); + respond("m\033\\"); + } + else if (strneq(buffer+2, " q", 2)) { + // DECSCUSR - Query cursor style + sprintf(buf, "\033[P1$r%d q\033\\", 1); + /* + Ps = 0 -> blinking block. + Ps = 1 -> blinking block (default). + Ps = 2 -> steady block. + Ps = 3 -> blinking underline. + Ps = 4 -> steady underline. + Ps = 5 -> blinking bar (xterm). + Ps = 6 -> steady bar (xterm). + */ + respond(buf); + } + else { + // invalid query + ansi_warn("NOIMPL: DCS %s ST", buffer); + sprintf(buf, "\033[P0$r%s\033\\", buffer+2); + } + } + else { + ansi_warn("NOIMPL: DCS %s ST", buffer); + } } void ICACHE_FLASH_ATTR -apars_handle_OSC_SetTitle(const char *buffer) +apars_handle_StrCmd(char leadchar, const char *buffer) { - strncpy(termconf_scratch.title, buffer, TERM_TITLE_LEN); -// info("OSC: Set TITLE = %s", buffer); - screen_notifyChange(CHANGE_LABELS); + switch (leadchar) { + case 'k': // ESC k TITLE ST (defined in GNU screen manpage) + set_title(buffer); + break; + case ']': // OSC - Operating System Command + parse_osc(buffer); + break; + case 'P': // DCS - Device Control String + parse_dcs(buffer); + break; + case '^': // PM - Privacy Message + break; + case '_': // APC - Application Program Command + break; + case 'X': // SOS - Start of String (purpose unclear) + break; + default: + ansi_warn("NOIMPL: ESC %c Pt ST", leadchar); + } } diff --git a/user/ansi_parser_callbacks.h b/user/ansi_parser_callbacks.h index 3126695..971decf 100644 --- a/user/ansi_parser_callbacks.h +++ b/user/ansi_parser_callbacks.h @@ -4,14 +4,12 @@ #include "screen.h" void apars_handle_plainchar(char c); -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_CSI(char leadchar, const int *params, int count, char keychar); +void apars_handle_StrCmd(char leadchar, 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_handle_spaceCmd(char c); void apars_reset_utf8buffer(void); void apars_handle_bel(void); diff --git a/user/screen.c b/user/screen.c index 75bbf01..8cc75f6 100644 --- a/user/screen.c +++ b/user/screen.c @@ -2,6 +2,7 @@ #include #include "screen.h" #include "persist.h" +#include "sgr.h" TerminalConfigBundle * const termconf = &persist.current.termconf; TerminalConfigBundle termconf_scratch; @@ -834,6 +835,22 @@ screen_set_newline_mode(bool nlm) scr.newline_mode = nlm; } +void ICACHE_FLASH_ATTR +screen_report_sgr(char *buffer) +{ + buffer += sprintf(buffer, "0"); + if (cursor.attrs & ATTR_BOLD) buffer += sprintf(buffer, ";%d", SGR_BOLD); + if (cursor.attrs & ATTR_FAINT) buffer += sprintf(buffer, ";%d", SGR_FAINT); + if (cursor.attrs & ATTR_ITALIC) buffer += sprintf(buffer, ";%d", SGR_ITALIC); + if (cursor.attrs & ATTR_UNDERLINE) buffer += sprintf(buffer, ";%d", SGR_UNDERLINE); + if (cursor.attrs & ATTR_BLINK) buffer += sprintf(buffer, ";%d", SGR_BLINK); + if (cursor.attrs & ATTR_FRAKTUR) buffer += sprintf(buffer, ";%d", SGR_FRAKTUR); + if (cursor.attrs & ATTR_STRIKE) buffer += sprintf(buffer, ";%d", SGR_STRIKE); + if (cursor.inverse) buffer += sprintf(buffer, ";%d", SGR_INVERSE); + if (cursor.fg != termconf->default_fg) buffer += sprintf(buffer, ";%d", ((cursor.fg > 7) ? SGR_FG_BRT_START : SGR_FG_START) + (cursor.fg&7)); + if (cursor.bg != termconf->default_bg) buffer += sprintf(buffer, ";%d", ((cursor.bg > 7) ? SGR_BG_BRT_START : SGR_BG_START) + (cursor.bg&7)); +} + //endregion //region --- Printing --- diff --git a/user/screen.h b/user/screen.h index ac04573..d745c62 100644 --- a/user/screen.h +++ b/user/screen.h @@ -90,6 +90,7 @@ void terminal_restore_defaults(void); void terminal_apply_settings(void); void terminal_apply_settings_noclear(void); // the same, but with no screen reset / init +void screen_report_sgr(char *buffer); typedef enum { CLEAR_TO_CURSOR=0, CLEAR_FROM_CURSOR=1, CLEAR_ALL=2 diff --git a/user/sgr.h b/user/sgr.h new file mode 100644 index 0000000..d87c3df --- /dev/null +++ b/user/sgr.h @@ -0,0 +1,37 @@ +// +// Created by MightyPork on 2017/08/19. +// + +#ifndef ESP_VT100_FIRMWARE_SGR_H +#define ESP_VT100_FIRMWARE_SGR_H + +enum SGR_CODES { + SGR_RESET = 0, + SGR_BOLD = 1, + SGR_FAINT = 2, + SGR_ITALIC = 3, + SGR_UNDERLINE = 4, + SGR_BLINK = 5, + SGR_BLINK_FAST = 6, + SGR_INVERSE = 7, + SGR_STRIKE = 9, + SGR_FRAKTUR = 20, + + SGR_FG_START = 30, + SGR_FG_END = 37, + SGR_FG_DEFAULT = 39, + + SGR_BG_START = 40, + SGR_BG_END = 47, + SGR_BG_DEFAULT = 49, + + SGR_FG_BRT_START = 90, + SGR_FG_BRT_END = 97, + + SGR_BG_BRT_START = 100, + SGR_BG_BRT_END = 107, +}; + +#define SGR_OFF(n) (20+(n)) + +#endif //ESP_VT100_FIRMWARE_SGR_H