diff --git a/CMakeLists.txt b/CMakeLists.txt index 3855e8d..e5d0fc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,14 +117,13 @@ set(SOURCE_FILES user/cgi_sockets.h user/ansi_parser_callbacks.c user/ansi_parser_callbacks.h - user/user_main.h user/wifimgr.c user/wifimgr.h user/persist.c user/persist.h include/helpers.h user/syscfg.c - user/syscfg.h user/ascii.h user/sgr.h) + user/syscfg.h user/ascii.h user/sgr.h user/apars_utf8.c user/apars_utf8.h user/apars_logging.h user/version.h user/apars_csi.c user/apars_csi.h user/apars_short.c user/apars_short.h user/apars_string.c user/apars_string.h user/apars_osc.c user/apars_osc.h user/apars_dcs.c user/apars_dcs.h) include_directories(include) include_directories(user) @@ -146,6 +145,7 @@ add_definitions( -DADMIN_PASSWORD="asdf" -DGIT_HASH="blabla" -DDEBUG_ANSI=1 + -DDEBUG_ANSI_NOIMPL=1 -DESPFS_HEATSHRINK) add_executable(esp_vt100_firmware ${SOURCE_FILES}) diff --git a/Makefile b/Makefile index 6e1a9d1..57c62bd 100644 --- a/Makefile +++ b/Makefile @@ -73,6 +73,7 @@ CFLAGS += -DADMIN_PASSWORD=$(ADMIN_PASSWORD) # Debug logging CFLAGS += -DDEBUG_ANSI=1 +CFLAGS += -DDEBUG_ANSI_NOIMPL=1 CFLAGS += -DDEBUG_INPUT=1 # linker flags used to generate the main object file diff --git a/user/ansi_parser.c b/user/ansi_parser.c index 9786271..0ed6ef1 100644 --- a/user/ansi_parser.c +++ b/user/ansi_parser.c @@ -2,9 +2,9 @@ /* #line 1 "user/ansi_parser.rl" */ #include #include "ansi_parser.h" -#include "screen.h" +#include "ansi_parser_callbacks.h" #include "ascii.h" -#include "uart_driver.h" +#include "apars_logging.h" /* Ragel constants block */ @@ -14,12 +14,12 @@ static const char _ansi_actions[] = { 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8, 1, 9, 1, 10, 1, 11, 1, 12, 1, 13, 1, 14, 2, - 3, 6, 2, 8, 9 + 3, 6 }; static const char _ansi_eof_actions[] = { 0, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0 + 1, 1, 0, 0, 0, 0, 0 }; static const int ansi_start = 1; @@ -27,7 +27,7 @@ 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_StrCmd_body = 7; +static const int ansi_en_STRCMD_body = 7; static const int ansi_en_charsetcmd_body = 9; static const int ansi_en_main = 1; @@ -62,7 +62,7 @@ static char history[HISTORY_LEN + 1]; #endif void ICACHE_FLASH_ATTR -apars_handle_badseq(void) +apars_show_context(void) { #if DEBUG_ANSI char buf1[HISTORY_LEN*3+2]; @@ -104,7 +104,6 @@ ansi_parser(char newchar) 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; @@ -114,12 +113,12 @@ ansi_parser(char newchar) // Init Ragel on the first run if (cs == -1) { -/* #line 118 "user/ansi_parser.c" */ +/* #line 117 "user/ansi_parser.c" */ { cs = ansi_start; } -/* #line 92 "user/ansi_parser.rl" */ +/* #line 91 "user/ansi_parser.rl" */ #if DEBUG_ANSI memset(history, 0, sizeof(history)); @@ -155,15 +154,16 @@ ansi_parser(char newchar) return; case TAB: - screen_tab_forward(1); + apars_handle_tab(); return; // Select G0 or G1 case SI: - screen_set_charset_n(1); + apars_handle_chs_switch(1); return; + case SO: - screen_set_charset_n(0); + apars_handle_chs_switch(0); return; case BEL: @@ -174,8 +174,8 @@ ansi_parser(char newchar) } break; - case ENQ: // respond with space (like xterm) - UART_WriteChar(UART0, SP, UART_TIMEOUT_US); + case ENQ: + apars_handle_enq(); return; // Cancel the active sequence @@ -217,9 +217,13 @@ ansi_parser(char newchar) _resume: switch ( cs ) { case 1: - if ( (*p) == 27 ) - goto tr1; + switch( (*p) ) { + case 7: goto tr1; + case 27: goto tr2; + } goto tr0; +case 0: + goto _out; case 2: switch( (*p) ) { case 32: goto tr3; @@ -250,24 +254,24 @@ case 2: goto tr6; } else goto tr6; - goto tr2; -case 0: - goto _out; + goto tr1; case 3: if ( (*p) > 71 ) { if ( 76 <= (*p) && (*p) <= 78 ) goto tr9; } else if ( (*p) >= 70 ) goto tr9; - goto tr2; + goto tr1; case 10: - if ( (*p) == 27 ) - goto tr1; + switch( (*p) ) { + case 7: goto tr1; + case 27: goto tr2; + } goto tr0; case 4: if ( 48 <= (*p) && (*p) <= 57 ) goto tr10; - goto tr2; + goto tr1; case 5: switch( (*p) ) { case 59: goto tr13; @@ -287,7 +291,7 @@ case 5: goto tr15; } else goto tr11; - goto tr2; + goto tr1; case 6: if ( (*p) == 59 ) goto tr13; @@ -299,9 +303,9 @@ case 6: goto tr15; } else goto tr15; - goto tr2; + goto tr1; case 11: - goto tr2; + goto tr1; case 12: if ( (*p) == 59 ) goto tr13; @@ -313,7 +317,7 @@ case 12: goto tr15; } else goto tr15; - goto tr2; + goto tr1; case 7: switch( (*p) ) { case 7: goto tr17; @@ -321,28 +325,24 @@ case 7: } goto tr16; case 13: - switch( (*p) ) { - case 7: goto tr17; - case 27: goto tr18; - } - goto tr16; + goto tr1; case 8: if ( (*p) == 92 ) - goto tr19; - goto tr2; -case 14: - goto tr2; + goto tr17; + goto tr1; case 9: - if ( (*p) == 27 ) - goto tr2; - goto tr20; -case 15: - goto tr2; + switch( (*p) ) { + case 7: goto tr1; + case 27: goto tr1; + } + goto tr19; +case 14: + goto tr1; } - tr2: cs = 0; goto f0; + tr1: cs = 0; goto f0; tr0: cs = 1; goto f1; - tr1: cs = 2; goto _again; + tr2: cs = 2; goto _again; tr3: cs = 3; goto _again; tr4: cs = 4; goto _again; tr11: cs = 6; goto f8; @@ -360,7 +360,6 @@ case 15: 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; @@ -371,14 +370,13 @@ case 15: 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; + f14: _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; + f15: _acts = _ansi_actions + 29; goto execFuncs; f11: _acts = _ansi_actions + 31; goto execFuncs; - f14: _acts = _ansi_actions + 34; goto execFuncs; execFuncs: _nacts = *_acts++; @@ -388,7 +386,7 @@ execFuncs: /* #line 185 "user/ansi_parser.rl" */ { ansi_warn("Parser error."); - apars_handle_badseq(); + apars_show_context(); inside_string = false; // no longer in string, for sure {cs = 1;goto _again;} } @@ -444,7 +442,7 @@ execFuncs: case 6: /* #line 234 "user/ansi_parser.rl" */ { - apars_handle_CSI(leadchar, arg, arg_cnt, (*p)); + apars_handle_csi(leadchar, arg, arg_cnt, (*p)); {cs = 1;goto _again;} } break; @@ -469,28 +467,28 @@ execFuncs: { inside_string = false; string_buffer[str_ni++] = '\0'; - apars_handle_StrCmd(leadchar, string_buffer); + apars_handle_string_cmd(leadchar, string_buffer); {cs = 1;goto _again;} } break; case 10: /* #line 270 "user/ansi_parser.rl" */ { - apars_handle_hashCode((*p)); + apars_handle_hash_cmd((*p)); {cs = 1;goto _again;} } break; case 11: /* #line 275 "user/ansi_parser.rl" */ { - apars_handle_shortCode((*p)); + apars_handle_short_cmd((*p)); {cs = 1;goto _again;} } break; case 12: /* #line 280 "user/ansi_parser.rl" */ { - apars_handle_spaceCmd((*p)); + apars_handle_space_cmd((*p)); {cs = 1;goto _again;} } break; @@ -504,11 +502,11 @@ execFuncs: case 14: /* #line 292 "user/ansi_parser.rl" */ { - apars_handle_characterSet(leadchar, (*p)); + apars_handle_chs_designate(leadchar, (*p)); {cs = 1;goto _again;} } break; -/* #line 512 "user/ansi_parser.c" */ +/* #line 510 "user/ansi_parser.c" */ } } goto _again; @@ -529,14 +527,14 @@ _again: /* #line 185 "user/ansi_parser.rl" */ { ansi_warn("Parser error."); - apars_handle_badseq(); + apars_show_context(); inside_string = false; // no longer in string, for sure {cs = 1; if ( p == pe ) goto _test_eof; goto _again;} } break; -/* #line 540 "user/ansi_parser.c" */ +/* #line 538 "user/ansi_parser.c" */ } } } diff --git a/user/ansi_parser.h b/user/ansi_parser.h index 72a2bee..8e813bd 100644 --- a/user/ansi_parser.h +++ b/user/ansi_parser.h @@ -2,32 +2,11 @@ #define ANSI_PARSER_H #include -#include - -extern void apars_handle_plainchar(char c); -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_spaceCmd(char c); -extern void apars_reset_utf8buffer(void); -extern void apars_handle_bel(void); void ansi_parser_reset(void); extern volatile u32 ansi_parser_char_cnt; -// defined in the makefile -#if DEBUG_ANSI -#define ansi_warn warn -#define ansi_dbg dbg -#else -#define ansi_warn(...) -#define ansi_dbg(...) -#endif - - /** * \brief Linear ANSI chars stream parser * @@ -43,6 +22,6 @@ extern volatile u32 ansi_parser_char_cnt; void ansi_parser(char newchar); /** This shows a short error message and prints the history (if any) */ -void apars_handle_badseq(void); +void apars_show_context(void); #endif // ANSI_PARSER_H diff --git a/user/ansi_parser.rl b/user/ansi_parser.rl index 77b34a4..f0c9dd4 100644 --- a/user/ansi_parser.rl +++ b/user/ansi_parser.rl @@ -1,8 +1,8 @@ #include #include "ansi_parser.h" -#include "screen.h" +#include "ansi_parser_callbacks.h" #include "ascii.h" -#include "uart_driver.h" +#include "apars_logging.h" /* Ragel constants block */ %%{ @@ -37,7 +37,7 @@ static char history[HISTORY_LEN + 1]; #endif void ICACHE_FLASH_ATTR -apars_handle_badseq(void) +apars_show_context(void) { #if DEBUG_ANSI char buf1[HISTORY_LEN*3+2]; @@ -79,7 +79,6 @@ ansi_parser(char newchar) 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; @@ -124,15 +123,16 @@ ansi_parser(char newchar) return; case TAB: - screen_tab_forward(1); + apars_handle_tab(); return; // Select G0 or G1 case SI: - screen_set_charset_n(1); + apars_handle_chs_switch(1); return; + case SO: - screen_set_charset_n(0); + apars_handle_chs_switch(0); return; case BEL: @@ -143,8 +143,8 @@ ansi_parser(char newchar) } break; - case ENQ: // respond with space (like xterm) - UART_WriteChar(UART0, SP, UART_TIMEOUT_US); + case ENQ: + apars_handle_enq(); return; // Cancel the active sequence @@ -176,15 +176,15 @@ ansi_parser(char newchar) %%{ #/* ESC = 27; - NOESC = (any - ESC); + NOESC = (any - ESC - 7); TOK_ST = ESC '\\'; # String terminator - used for OSC commands - STR_END = ('\a' | TOK_ST); + STR_END = (7 | TOK_ST); # --- Error handler --- action errBadSeq { ansi_warn("Parser error."); - apars_handle_badseq(); + apars_show_context(); inside_string = false; // no longer in string, for sure fgoto main; } @@ -232,7 +232,7 @@ ansi_parser(char newchar) } action CSI_end { - apars_handle_CSI(leadchar, arg, arg_cnt, fc); + apars_handle_csi(leadchar, arg, arg_cnt, fc); fgoto main; } @@ -247,7 +247,7 @@ ansi_parser(char newchar) str_ni = 0; string_buffer[0] = '\0'; inside_string = true; - fgoto StrCmd_body; + fgoto STRCMD_body; } action StrCmd_char { @@ -257,28 +257,28 @@ ansi_parser(char newchar) action StrCmd_end { inside_string = false; string_buffer[str_ni++] = '\0'; - apars_handle_StrCmd(leadchar, string_buffer); + apars_handle_string_cmd(leadchar, string_buffer); fgoto main; } # 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; + STRCMD_body := ((NOESC @StrCmd_char)* STR_END @StrCmd_end) $!errBadSeq; # --- Single character ESC --- action HASH_code { - apars_handle_hashCode(fc); + apars_handle_hash_cmd(fc); fgoto main; } action SHORT_code { - apars_handle_shortCode(fc); + apars_handle_short_cmd(fc); fgoto main; } action SPACE_cmd { - apars_handle_spaceCmd(fc); + apars_handle_space_cmd(fc); fgoto main; } @@ -290,7 +290,7 @@ ansi_parser(char newchar) } action CharsetCmd_end { - apars_handle_characterSet(leadchar, fc); + apars_handle_chs_designate(leadchar, fc); fgoto main; } diff --git a/user/ansi_parser_callbacks.c b/user/ansi_parser_callbacks.c index 30b32ec..35c9ea6 100644 --- a/user/ansi_parser_callbacks.c +++ b/user/ansi_parser_callbacks.c @@ -7,123 +7,18 @@ #include #include #include "ansi_parser_callbacks.h" -#include "screen.h" -#include "ansi_parser.h" #include "uart_driver.h" -#include "sgr.h" #include "cgi_sockets.h" -#include "ascii.h" -#include "user_main.h" -#include "syscfg.h" - -static char utf_collect[4]; -static int utf_i = 0; -static int utf_j = 0; - -/** - * Handle a received plain character - * @param c - received character - */ -void ICACHE_FLASH_ATTR -apars_handle_plainchar(char c) -{ - // collecting unicode glyphs... - if (c & 0x80) { - if (utf_i == 0) { - // start - if (c == 192 || c == 193 || c >= 245) { - // forbidden codes - goto fail; - } - - if ((c & 0xE0) == 0xC0) { - utf_i = 2; - } - else if ((c & 0xF0) == 0xE0) { - utf_i = 3; - } - else if ((c & 0xF8) == 0xF0) { - utf_i = 4; - } - else { - // chars over 127 that don't start unicode sequences - goto fail; - } - - utf_collect[0] = c; - utf_j = 1; - } - else { - if ((c & 0xC0) != 0x80) { - goto fail; - } - else { - utf_collect[utf_j++] = c; - if (utf_j >= utf_i) { - screen_putchar(utf_collect); - apars_reset_utf8buffer(); - } - } - } - } - else { - utf_collect[0] = c; - utf_collect[1] = 0; // just to make sure it's closed... - screen_putchar(utf_collect); - } - - return; - 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) -{ - utf_i = 0; - utf_j = 0; - memset(utf_collect, 0, 4); -} +#include "version.h" /** * 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 (this sets 8/7-bit mode and some other archaic options) - * @param c - key character - */ void ICACHE_FLASH_ATTR -apars_handle_spaceCmd(char c) +apars_respond(const char *str) { - ansi_warn("NOIMPL: ESC SP %c", c); + UART_WriteString(UART0, str, UART_TIMEOUT_US); } /** @@ -135,792 +30,18 @@ apars_handle_bel(void) send_beep(); } -// data tables for the DECREPTPARM command response - -struct DECREPTPARM_parity {int parity; const char * msg;}; -static const struct DECREPTPARM_parity DECREPTPARM_parity_arr[] = { - {PARITY_NONE, "1"}, - {PARITY_ODD, "4"}, - {PARITY_EVEN, "5"}, - {-1, 0} -}; - -struct DECREPTPARM_baud {int baud; const char * msg;}; -static const struct DECREPTPARM_baud DECREPTPARM_baud_arr[] = { - {BIT_RATE_300, "48"}, - {BIT_RATE_600, "56"}, - {BIT_RATE_1200, "64"}, - {BIT_RATE_2400, "88"}, - {BIT_RATE_4800, "96"}, - {BIT_RATE_9600 , "104"}, - {BIT_RATE_19200 , "112"}, - {BIT_RATE_38400 , "120"}, - {BIT_RATE_57600 , "128"}, // this is the last in the spec, follow +8 - {BIT_RATE_74880 , "136"}, - {BIT_RATE_115200, "144"}, - {BIT_RATE_230400, "152"}, - {BIT_RATE_460800, "160"}, - {BIT_RATE_921600, "168"}, - {BIT_RATE_1843200, "176"}, - {BIT_RATE_3686400, "184"}, - {-1, 0} -}; - /** - * 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 + * Send to uart the answerback message */ void ICACHE_FLASH_ATTR -apars_handle_CSI(char leadchar, const int *params, int count, char keychar) -{ - int n1 = params[0]; - int n2 = params[1]; - int n3 = params[2]; - char buf[32]; - bool yn = false; // for ? l h - - // defaults - FIXME this may inadvertently affect some variants that should be left unchanged - switch (keychar) { - case 'A': // move - case 'B': - case 'C': - case 'D': - case 'E': - case 'F': - case 'G': // set X - case '`': - case 'S': // scrolling - case 'T': - case 'X': // clear in line - case 'd': // set Y - case 'L': - case 'M': - case '@': - case 'P': - case 'I': - case 'Z': - case 'b': - if (n1 == 0) n1 = 1; - break; - - case 'H': - case 'f': - if (n1 == 0) n1 = 1; - if (n2 == 0) n2 = 1; - break; - - case 'J': - case 'K': - if (n1 > 2) n1 = 0; - break; - - default: - // leave as is - break; - } - - switch (keychar) { - // CUU CUD CUF CUB - case 'a': - case 'A': // Up - screen_cursor_move(-n1, 0, false); - break; - - case 'e': - case 'B': // Down - screen_cursor_move(n1, 0, false); - break; - - case 'C': // Right (forward) - screen_cursor_move(0, n1, false); - break; - - case 'D': // Left (backward) - screen_cursor_move(0, -n1, false); - break; - - case 'E': // CNL - Cursor Next Line - screen_cursor_move(n1, 0, false); - screen_cursor_set_x(0); - break; - - case 'F': // CPL - Cursor Prev Line - screen_cursor_move(-n1, 0, false); - screen_cursor_set_x(0); - break; - - case 'b': - // TODO repeat preceding graphic character n1 times - ansi_warn("NOIMPL: Repeat char"); - return; - - // Set X - case 'G': - case '`': // alternate code - screen_cursor_set_x(n1 - 1); - break; // 1-based - - // Set Y - case 'd': - screen_cursor_set_y(n1 - 1); - break; // 1-based - - // Clear in line - case 'X': - screen_clear_in_line(n1); - break; // 1-based - - // SU, SD - scroll up/down - case 'S': - if (leadchar == NUL && count <= 1) { - screen_scroll_up(n1); - } - else { - // other: - // CSI ? Pi; Pa; Pv S (sixel) - ansi_warn("NOIMPL: CSI"); - apars_handle_badseq(); - } - break; - case 'T': - if (leadchar == NUL && count <= 1) { - // CSI Ps T - screen_scroll_down(n1); - } - else { - // other: - // CSI Ps ; Ps ; Ps ; Ps ; Ps T - // CSI > Ps; Ps T - ansi_warn("NOIMPL: CSI"); - apars_handle_badseq(); - } - break; - - case 't': // xterm window commands - if (leadchar == NUL && count <= 2) { - // CSI Ps ; Ps ; Ps t - 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); - respond(buf); - break; - case 11: // Report iconified -> is not iconified - respond("\033[1t"); - break; - case 21: // Report title - 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; - } - } - else { - // other: - // CSI > Ps; Ps t - // CSI Ps SP t, - ansi_warn("NOIMPL: CSI"); - apars_handle_badseq(); - } - break; - - // CUP,HVP - set position - case 'H': - case 'f': - screen_cursor_set(n1-1, n2-1); - break; // 1-based - - case 'J': // Erase screen - if (leadchar == '?') { - // TODO selective erase - ansi_warn("NOIMPL: Selective erase"); - } - - if (n1 == 0) { - screen_clear(CLEAR_FROM_CURSOR); - } else if (n1 == 1) { - screen_clear(CLEAR_TO_CURSOR); - } else { - screen_clear(CLEAR_ALL); - screen_cursor_set(0, 0); - } - break; - - case 'K': // Erase lines - if (leadchar == '?') { - // TODO selective erase - ansi_warn("NOIMPL: Selective erase"); - } - - if (n1 == 0) { - screen_clear_line(CLEAR_FROM_CURSOR); - } else if (n1 == 1) { - screen_clear_line(CLEAR_TO_CURSOR); - } else { - screen_clear_line(CLEAR_ALL); - } - break; - - // SCP, RCP - save/restore position - case 's': - if (leadchar == NUL && count == 0) { - screen_cursor_save(0); - } - else { - // other: - // CSI ? Pm s - // CSI Pl; Pr s - ansi_warn("NOIMPL: CSI"); - apars_handle_badseq(); - } - break; - - case 'u': - if (leadchar == NUL && count == 0) { - screen_cursor_restore(0); - } - else { - ansi_warn("NOIMPL: CSI"); - apars_handle_badseq(); - } - break; - - case 'n': // Queries - if (leadchar == '>') { - // some xterm garbage - discard - // CSI > Ps n - ansi_warn("NOIMPL: CSI > %d n", n1); - break; - } - - if (n1 == 6) { - // Query cursor position - int x, y; - screen_cursor_get(&y, &x); - sprintf(buf, "\033[%d;%dR", y+1, x+1); - respond(buf); - } - else if (n1 == 5) { - // Query device status - reply "Device is OK" - respond("\033[0n"); - } - else { - ansi_warn("NOIMPL: CSI"); - apars_handle_badseq(); - } - break; - - case 'h': // DEC feature enable - yn = 1; - case 'l': // DEC feature disable - // yn is 0 by default - for (int i = 0; i < count; i++) { - int n = params[i]; - if (leadchar == '?') { - if (n == 1) { - screen_set_cursors_alt_mode(yn); - } - else if (n == 2) { - // should reset all Gx to USASCII and reset to VT100 (which we use always) - screen_set_charset(0, 'B'); - screen_set_charset(1, 'B'); - } - else if (n == 3) { - // TODO 132 column mode - not implemented due to RAM demands - ansi_warn("NOIMPL: 80->132"); - } - else if (n == 4) { - // Smooth scroll - not implemented - } - else if (n == 5) { - screen_set_reverse_video(yn); - } - else if (n == 6) { - screen_set_origin_mode(yn); - } - else if (n == 7) { - screen_wrap_enable(yn); - } - else if (n == 8) { - // TODO Key auto-repeat - // We don't implement this currently, but it could be added - // - discard repeated keypress events between keydown and keyup. - ansi_warn("NOIMPL: Auto-repeat toggle"); - } - 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 - ansi_warn("NOIMPL: Mouse tracking"); - } - else if (n == 12) { - // TODO Cursor blink on/off - ansi_warn("NOIMPL: Cursor blink toggle"); - } - else if (n == 40) { - // TODO allow/disallow 80->132 mode - // not implemented because of RAM demands - ansi_warn("NOIMPL: 80->132 enable"); - } - else if (n == 47 || n == 1047) { - // Switch to/from alternate screen - // - not implemented fully due to RAM demands - screen_swap_state(yn); - } - else if (n == 1048) { - // same as DECSC - save/restore cursor with attributes - if (yn) { - screen_cursor_save(true); - } - else { - screen_cursor_restore(true); - } - } - else if (n == 1049) { - // save/restore cursor and screen and clear it - if (yn) { - screen_cursor_save(true); - screen_swap_state(true); // this should save the screen - can't because of RAM size - screen_clear(CLEAR_ALL); - } - else { - screen_clear(CLEAR_ALL); - screen_swap_state(false); // this should restore the screen - can't because of RAM size - screen_cursor_restore(true); - } - } - else if (n >= 1050 && n <= 1053) { - // TODO Different kinds of function key emulation ? - // (In practice this seems hardly ever used) - - // Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode. - // Ps = 1 0 5 1 -> Set Sun function-key mode. - // Ps = 1 0 5 2 -> Set HP function-key mode. - // Ps = 1 0 5 3 -> Set SCO function-key mode. - ansi_warn("NOIMPL: FN key emul type"); - } - else if (n == 2004) { - // Bracketed paste mode - // Discard, we don't implement this - } - else if (n == 25) { - screen_set_cursor_visible(yn); - } - else { - ansi_warn("NOIMPL: CSI ? %d %c", n, keychar); - } - } - else { - if (n == 4) { - screen_set_insert_mode(yn); - } - else if (n == 20) { - screen_set_newline_mode(yn); - } - else { - ansi_warn("NOIMPL: CSI %d %c", n, keychar); - } - } - } - break; - - case 'm': // SGR - set graphics rendition - if (count == 0) { - count = 1; // this makes it work as 0 (reset) - } - - if (leadchar == '>') { - // some xterm garbage - discard - // CSI > Ps; Ps m - break; - } - - // iterate arguments - for (int i = 0; i < count; i++) { - int n = params[i]; - - 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_set_sgr(ATTR_BOLD, 1); - else if (n == SGR_FAINT) screen_set_sgr(ATTR_FAINT, 1); - else if (n == SGR_ITALIC) screen_set_sgr(ATTR_ITALIC, 1); - else if (n == SGR_UNDERLINE) screen_set_sgr(ATTR_UNDERLINE, 1); - else if (n == SGR_BLINK || n == SGR_BLINK_FAST) screen_set_sgr(ATTR_BLINK, 1); // 6 - rapid blink, not supported - else if (n == SGR_STRIKE) screen_set_sgr(ATTR_STRIKE, 1); - else if (n == SGR_FRAKTUR) screen_set_sgr(ATTR_FRAKTUR, 1); - else if (n == SGR_INVERSE) screen_set_sgr_inverse(1); - // -- clear attr -- - else if (n == SGR_OFF(SGR_BOLD)) screen_set_sgr(ATTR_BOLD, 0); - else if (n == SGR_OFF(SGR_FAINT)) screen_set_sgr(ATTR_FAINT, 0); - else if (n == SGR_OFF(SGR_ITALIC)) screen_set_sgr(ATTR_ITALIC | ATTR_FRAKTUR, 0); // there is no dedicated OFF code for Fraktur - else if (n == SGR_OFF(SGR_UNDERLINE)) screen_set_sgr(ATTR_UNDERLINE, 0); - else if (n == SGR_OFF(SGR_BLINK)) screen_set_sgr(ATTR_BLINK, 0); - else if (n == SGR_OFF(SGR_STRIKE)) screen_set_sgr(ATTR_STRIKE, 0); - else if (n == SGR_OFF(SGR_INVERSE)) screen_set_sgr_inverse(0); - // -- 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 %d", n); - } - } - break; - - case 'L': // Insert lines (shove down) - screen_insert_lines(n1); - break; - - case 'M': // Delete lines (pull up) - screen_delete_lines(n1); - break; - - case '@': // Insert in line (shove right) - screen_insert_characters(n1); - break; - - case 'P': // Delete in line (pull left) - screen_delete_characters(n1); - break; - - case 'r': - if (leadchar == NUL && count == 2) { - screen_set_scrolling_region(n1, n2); - } - else { - // other: - // CSI ? Pm r - // CSI Pt; Pl; Pb; Pr; Ps$ r - ansi_warn("NOIMPL: CSI"); - apars_handle_badseq(); - } - break; - - case 'g': // Clear tabs - if (n1 == 3) { - screen_clear_all_tabs(); - } else { - screen_clear_tab(); - } - break; - - case 'Z': // Tab backward - screen_tab_reverse(n1); - break; - - case 'I': // Tab forward - screen_tab_forward(n1); - break; - - case 'c': // CSI-c - report capabilities - if (leadchar == NUL) { - respond("\033[?64;9c"); // pretend we're vt400 with national character sets - } - else if (leadchar == '>') { - // 41 - we're "VT400", 0 - ROM cartridge number - sprintf(buf, "\033[>41;%d;0c", FIRMWARE_VERSION_NUM); - respond(buf); - } else { - ansi_warn("NOIMPL: CSI"); - apars_handle_badseq(); - } - break; - - case 'x': // DECREQTPARM -> DECREPTPARM - if (n1 <= 1) { - respond("\033[3;"); // this is a response on request (2 would be gratuitous) - - // Parity - for(const struct DECREPTPARM_parity *p = DECREPTPARM_parity_arr; p->parity != -1; p++) { - if (p->parity == sysconf->uart_parity) { - respond(p->msg); - break; - } - } - - // bits per character (uart byte) - respond(";8;"); - - // Baud rate - for(const struct DECREPTPARM_baud *p = DECREPTPARM_baud_arr; p->baud != -1; p++) { - if (p->baud == sysconf->uart_baudrate) { - respond(p->msg); - respond(";"); - respond(p->msg); - break; - } - } - - // multiplier 1, flags 0 - respond(";1;0x"); // ROM cartridge number ?? - } - break; - - case 'p': - if (leadchar == '!') { // RIS - /* On real VT there are differences between soft and hard reset, we treat both equally */ - screen_reset(); - } else { - ansi_warn("NOIMPL: CSI"); - apars_handle_badseq(); - } - break; - - default: - ansi_warn("NOIMPL: CSI Pm %c", keychar); - apars_handle_badseq(); - } -} - -/** - * Codes in the format ESC # n - * @param c - the trailing symbol (numeric ASCII) - */ -void ICACHE_FLASH_ATTR apars_handle_hashCode(char c) -{ - switch(c) { - case '3': // Double size, top half - case '4': // Single size, bottom half - case '5': // Single width, single height - case '6': // Double width - ansi_warn("NOIMPL: Double Size Line"); - break; - - case '8': - screen_fill_with_E(); - break; - - default: - ansi_warn("NOIMPL: ESC # %c", c); - } -} - -/** - * Single-character escape codes (ESC x) - * @param c - the trailing symbol (ASCII) - */ -void ICACHE_FLASH_ATTR apars_handle_shortCode(char c) -{ - switch(c) { - case 'c': // screen reset - screen_reset(); - break; - - case '7': // save cursor + attributes - screen_cursor_save(true); - break; - - case '8': // restore cursor + attributes - screen_cursor_restore(true); - break; - - case 'E': // same as CR LF - screen_cursor_move(1, 0, false); - screen_cursor_set_x(0); - break; - - case 'F': // bottom left - screen_cursor_set(termconf_scratch.height-1, 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': - screen_set_tab(); - break; - - case '>': - screen_set_numpad_alt_mode(false); - break; - - 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("NOIMPL: ESC %c", c); - } -} - -/** - * Helper function to set terminal title - * @param str - title text - */ -static void ICACHE_FLASH_ATTR -set_title(const char *str) -{ - strncpy(termconf_scratch.title, str, TERM_TITLE_LEN); - screen_notifyChange(CHANGE_LABELS); -} - -/** - * 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); - } -} - -/** - * 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) +apars_handle_enq(void) { - 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); - } + // version encased in SOS and ST + apars_respond("\x1bXESPTerm " FIRMWARE_VERSION "\x1b\\"); } void ICACHE_FLASH_ATTR -apars_handle_StrCmd(char leadchar, const char *buffer) +apars_handle_tab(void) { - 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); - } + screen_tab_forward(1); } diff --git a/user/ansi_parser_callbacks.h b/user/ansi_parser_callbacks.h index 971decf..0330641 100644 --- a/user/ansi_parser_callbacks.h +++ b/user/ansi_parser_callbacks.h @@ -1,16 +1,19 @@ #ifndef ANSI_PARSER_CALLBACKS_H #define ANSI_PARSER_CALLBACKS_H -#include "screen.h" +#include "apars_csi.h" +#include "apars_dcs.h" +#include "apars_osc.h" +#include "apars_string.h" +#include "apars_short.h" +#include "apars_utf8.h" + +void apars_respond(const char *str); -void apars_handle_plainchar(char c); -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_spaceCmd(char c); -void apars_reset_utf8buffer(void); void apars_handle_bel(void); +void apars_handle_enq(void); +void apars_handle_tab(void); + +extern void apars_show_context(void); #endif //ESP_VT100_FIRMWARE_ANSI_PARSER_CALLBACKS_H diff --git a/user/apars_csi.c b/user/apars_csi.c new file mode 100644 index 0000000..0601d15 --- /dev/null +++ b/user/apars_csi.c @@ -0,0 +1,596 @@ +// +// Created by MightyPork on 2017/08/20. +// +// Handle CSI sequences +// CSI Pm +// (CSI = ESC [) +// +// Example of those are cursor manipulation sequences and SGR. +// +// For details, see: +// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Functions-using-CSI-_-ordered-by-the-final-character_s_ +// +// Note: +// not all sequences listed in the xterm manual are implemented, notably sequences with the trailing symbol, +// graphic mode sequences, mouse reporting and complex multi-argument sequences that operate on regions. +// +// The screen size can be set using the xterm sequence: CSI Py ; Px t +// + +#include +#include "apars_csi.h" +#include "screen.h" +#include "apars_logging.h" +#include "ansi_parser.h" +#include "ascii.h" +#include "ansi_parser_callbacks.h" +#include "uart_driver.h" +#include "sgr.h" +#include "version.h" +#include "syscfg.h" + +// TODO simplify file - split to subroutines + +// data tables for the DECREPTPARM command response + +struct DECREPTPARM_parity { int parity; const char * msg; }; +static const struct DECREPTPARM_parity DECREPTPARM_parity_arr[] = { + {PARITY_NONE, "1"}, + {PARITY_ODD, "4"}, + {PARITY_EVEN, "5"}, + {-1, 0} +}; + +struct DECREPTPARM_baud { int baud; const char * msg; }; +static const struct DECREPTPARM_baud DECREPTPARM_baud_arr[] = { + {BIT_RATE_300, "48"}, + {BIT_RATE_600, "56"}, + {BIT_RATE_1200, "64"}, + {BIT_RATE_2400, "88"}, + {BIT_RATE_4800, "96"}, + {BIT_RATE_9600 , "104"}, + {BIT_RATE_19200 , "112"}, + {BIT_RATE_38400 , "120"}, + {BIT_RATE_57600 , "128"}, // this is the last in the spec, follow +8 + {BIT_RATE_74880 , "136"}, + {BIT_RATE_115200, "144"}, + {BIT_RATE_230400, "152"}, + {BIT_RATE_460800, "160"}, + {BIT_RATE_921600, "168"}, + {BIT_RATE_1843200, "176"}, + {BIT_RATE_3686400, "184"}, + {-1, 0} +}; + +static void warn_bad_csi() +{ + ansi_noimpl_r("Unknown CSI"); + apars_show_context(); +} + +/** + * 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, const int *params, int count, char keychar) +{ + int n1 = params[0]; + int n2 = params[1]; + int n3 = params[2]; + char buf[32]; + bool yn = false; // for ? l h + + // defaults - FIXME this may inadvertently affect some variants that should be left unchanged + switch (keychar) { + case 'A': // move + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': // set X + case '`': + case 'S': // scrolling + case 'T': + case 'X': // clear in line + case 'd': // set Y + case 'L': + case 'M': + case '@': + case 'P': + case 'I': + case 'Z': + case 'b': + if (n1 == 0) n1 = 1; + break; + + case 'H': + case 'f': + if (n1 == 0) n1 = 1; + if (n2 == 0) n2 = 1; + break; + + case 'J': + case 'K': + if (n1 > 2) n1 = 0; + break; + + default: + // leave as is + break; + } + + switch (keychar) { + // CUU CUD CUF CUB + case 'a': + case 'A': // Up + screen_cursor_move(-n1, 0, false); + break; + + case 'e': + case 'B': // Down + screen_cursor_move(n1, 0, false); + break; + + case 'C': // Right (forward) + screen_cursor_move(0, n1, false); + break; + + case 'D': // Left (backward) + screen_cursor_move(0, -n1, false); + break; + + case 'E': // CNL - Cursor Next Line + screen_cursor_move(n1, 0, false); + screen_cursor_set_x(0); + break; + + case 'F': // CPL - Cursor Prev Line + screen_cursor_move(-n1, 0, false); + screen_cursor_set_x(0); + break; + + case 'b': + // TODO repeat preceding graphic character n1 times + ansi_noimpl("Repeat char"); + return; + + // Set X + case 'G': + case '`': // alternate code + screen_cursor_set_x(n1 - 1); + break; // 1-based + + // Set Y + case 'd': + screen_cursor_set_y(n1 - 1); + break; // 1-based + + // Clear in line + case 'X': + screen_clear_in_line(n1); + break; // 1-based + + // SU, SD - scroll up/down + case 'S': + if (leadchar == NUL && count <= 1) { + screen_scroll_up(n1); + } + else { + // other: + // CSI ? Pi; Pa; Pv S (sixel) + warn_bad_csi(); + } + break; + + case 'T': + if (leadchar == NUL && count <= 1) { + // CSI Ps T + screen_scroll_down(n1); + } + else { + // other: + // CSI Ps ; Ps ; Ps ; Ps ; Ps T + // CSI > Ps; Ps T + warn_bad_csi(); + } + break; + + case 't': // xterm window commands + if (leadchar == NUL && count <= 2) { + // CSI Ps ; Ps ; Ps t + 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); + apars_respond(buf); + break; + case 11: // Report iconified -> is not iconified + apars_respond("\033[1t"); + break; + case 21: // Report title + apars_respond("\033]L"); + apars_respond(termconf_scratch.title); + apars_respond("\033\\"); + break; + case 24: // Set Height only + screen_resize(n2, termconf_scratch.width); + break; + default: + ansi_noimpl("CSI %d t", n1); + break; + } + } + else { + // other: + // CSI > Ps; Ps t + // CSI Ps SP t, + warn_bad_csi(); + } + break; + + // CUP,HVP - set position + case 'H': + case 'f': + screen_cursor_set(n1-1, n2-1); + break; // 1-based + + case 'J': // Erase screen + if (leadchar == '?') { + // TODO selective erase + ansi_noimpl("Selective erase"); + } + + if (n1 == 0) { + screen_clear(CLEAR_FROM_CURSOR); + } else if (n1 == 1) { + screen_clear(CLEAR_TO_CURSOR); + } else { + screen_clear(CLEAR_ALL); + screen_cursor_set(0, 0); + } + break; + + case 'K': // Erase lines + if (leadchar == '?') { + // TODO selective erase + ansi_noimpl("Selective erase"); + } + + if (n1 == 0) { + screen_clear_line(CLEAR_FROM_CURSOR); + } else if (n1 == 1) { + screen_clear_line(CLEAR_TO_CURSOR); + } else { + screen_clear_line(CLEAR_ALL); + } + break; + + // SCP, RCP - save/restore position + case 's': + if (leadchar == NUL && count == 0) { + screen_cursor_save(0); + } + else { + // other: + // CSI ? Pm s + // CSI Pl; Pr s + warn_bad_csi(); + } + break; + + case 'u': + if (leadchar == NUL && count == 0) { + screen_cursor_restore(0); + } + else { + warn_bad_csi(); + } + break; + + case 'n': // Queries + if (leadchar == '>') { + // some xterm garbage - discard + // CSI > Ps n + ansi_noimpl("CSI > %d n", n1); + break; + } + + if (n1 == 6) { + // Query cursor position + int x, y; + screen_cursor_get(&y, &x); + sprintf(buf, "\033[%d;%dR", y+1, x+1); + apars_respond(buf); + } + else if (n1 == 5) { + // Query device status - reply "Device is OK" + apars_respond("\033[0n"); + } + else { + warn_bad_csi(); + } + break; + + case 'h': // DEC feature enable + yn = 1; + case 'l': // DEC feature disable + // yn is 0 by default + for (int i = 0; i < count; i++) { + int n = params[i]; + if (leadchar == '?') { + if (n == 1) { + screen_set_cursors_alt_mode(yn); + } + else if (n == 2) { + // should reset all Gx to USASCII and reset to VT100 (which we use always) + screen_set_charset(0, 'B'); + screen_set_charset(1, 'B'); + } + else if (n == 3) { + // 132 column mode - not implemented due to RAM demands +// ansi_noimpl("80->132"); + } + else if (n == 4) { + // Smooth scroll - not implemented + } + else if (n == 5) { + screen_set_reverse_video(yn); + } + else if (n == 6) { + screen_set_origin_mode(yn); + } + else if (n == 7) { + screen_wrap_enable(yn); + } + else if (n == 8) { + // Key auto-repeat + // We don't implement this currently, but it could be added + // - discard repeated keypress events between keydown and keyup. +// ansi_noimpl("Auto-repeat toggle"); + } + 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 + ansi_noimpl("Mouse tracking"); + } + else if (n == 12) { + // TODO Cursor blink on/off + ansi_noimpl("Cursor blink toggle"); + } + else if (n == 40) { + // allow/disallow 80->132 mode + // not implemented because of RAM demands + ansi_noimpl("80->132 enable"); + } + else if (n == 45) { + // reverse wrap-around + ansi_noimpl("Reverse Wraparound"); + } + else if (n == 69) { + // horizontal margins + ansi_noimpl("Left/right margin"); + } + else if (n == 47 || n == 1047) { + // Switch to/from alternate screen + // - not implemented fully due to RAM demands + screen_swap_state(yn); + } + else if (n == 1048) { + // same as DECSC - save/restore cursor with attributes + if (yn) { + screen_cursor_save(true); + } + else { + screen_cursor_restore(true); + } + } + else if (n == 1049) { + // save/restore cursor and screen and clear it + if (yn) { + screen_cursor_save(true); + screen_swap_state(true); // this should save the screen - can't because of RAM size + screen_clear(CLEAR_ALL); + } + else { + screen_clear(CLEAR_ALL); + screen_swap_state(false); // this should restore the screen - can't because of RAM size + screen_cursor_restore(true); + } + } + else if (n >= 1050 && n <= 1053) { + // TODO Different kinds of function key emulation ? + // (In practice this seems hardly ever used) + + // Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode. + // Ps = 1 0 5 1 -> Set Sun function-key mode. + // Ps = 1 0 5 2 -> Set HP function-key mode. + // Ps = 1 0 5 3 -> Set SCO function-key mode. + ansi_noimpl("FN key emul type"); + } + else if (n == 2004) { + // Bracketed paste mode + // Discard, we don't implement this + } + else if (n == 25) { + screen_set_cursor_visible(yn); + } + else { + ansi_noimpl("CSI ? %d %c", n, keychar); + } + } + else { + if (n == 4) { + screen_set_insert_mode(yn); + } + else if (n == 20) { + screen_set_newline_mode(yn); + } + else { + ansi_noimpl("CSI %d %c", n, keychar); + } + } + } + break; + + case 'm': // SGR - set graphics rendition + if (count == 0) { + count = 1; // this makes it work as 0 (reset) + } + + if (leadchar == '>') { + // some xterm garbage - discard + // CSI > Ps; Ps m + break; + } + + // iterate arguments + for (int i = 0; i < count; i++) { + int n = params[i]; + + 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_set_sgr(ATTR_BOLD, 1); + else if (n == SGR_FAINT) screen_set_sgr(ATTR_FAINT, 1); + else if (n == SGR_ITALIC) screen_set_sgr(ATTR_ITALIC, 1); + else if (n == SGR_UNDERLINE) screen_set_sgr(ATTR_UNDERLINE, 1); + else if (n == SGR_BLINK || n == SGR_BLINK_FAST) screen_set_sgr(ATTR_BLINK, 1); // 6 - rapid blink, not supported + else if (n == SGR_STRIKE) screen_set_sgr(ATTR_STRIKE, 1); + else if (n == SGR_FRAKTUR) screen_set_sgr(ATTR_FRAKTUR, 1); + else if (n == SGR_INVERSE) screen_set_sgr_inverse(1); + // -- clear attr -- + else if (n == SGR_OFF(SGR_BOLD)) screen_set_sgr(ATTR_BOLD, 0); + else if (n == SGR_OFF(SGR_FAINT)) screen_set_sgr(ATTR_FAINT, 0); + else if (n == SGR_OFF(SGR_ITALIC)) screen_set_sgr(ATTR_ITALIC | ATTR_FRAKTUR, 0); // there is no dedicated OFF code for Fraktur + else if (n == SGR_OFF(SGR_UNDERLINE)) screen_set_sgr(ATTR_UNDERLINE, 0); + else if (n == SGR_OFF(SGR_BLINK)) screen_set_sgr(ATTR_BLINK, 0); + else if (n == SGR_OFF(SGR_STRIKE)) screen_set_sgr(ATTR_STRIKE, 0); + else if (n == SGR_OFF(SGR_INVERSE)) screen_set_sgr_inverse(0); + // -- 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_noimpl("SGR %d", n); + } + } + break; + + case 'L': // Insert lines (shove down) + screen_insert_lines(n1); + break; + + case 'M': // Delete lines (pull up) + screen_delete_lines(n1); + break; + + case '@': // Insert in line (shove right) + screen_insert_characters(n1); + break; + + case 'P': // Delete in line (pull left) + screen_delete_characters(n1); + break; + + case 'r': + if (leadchar == NUL && (count == 2 || count == 0)) { + screen_set_scrolling_region(n1, n2); + } + else { + // other: + // CSI ? Pm r + // CSI Pt; Pl; Pb; Pr; Ps$ r + warn_bad_csi(); + } + break; + + case 'g': // Clear tabs + if (n1 == 3) { + screen_clear_all_tabs(); + } else { + screen_clear_tab(); + } + break; + + case 'Z': // Tab backward + screen_tab_reverse(n1); + break; + + case 'I': // Tab forward + screen_tab_forward(n1); + break; + + case 'c': // CSI-c - report capabilities + if (leadchar == NUL) { + apars_respond("\033[?64;9c"); // pretend we're vt400 with national character sets + } + else if (leadchar == '>') { + // 41 - we're "VT400", 0 - ROM cartridge number + sprintf(buf, "\033[>41;%d;0c", FIRMWARE_VERSION_NUM); + apars_respond(buf); + } else { + warn_bad_csi(); + } + break; + + case 'x': // DECREQTPARM -> DECREPTPARM + // reference http://vt100.net/docs/vt100-ug/chapter3.html - search DECREPTPARM + if (n1 <= 1) { + apars_respond("\033["); // this is a response on request (2 would be gratuitous) + + apars_respond(n1 == 0 ? "2;" : "3;"); + + // Parity + for(const struct DECREPTPARM_parity *p = DECREPTPARM_parity_arr; p->parity != -1; p++) { + if (p->parity == sysconf->uart_parity) { + apars_respond(p->msg); + break; + } + } + + // bits per character (uart byte) - 1 = 8, 2 = 7 + apars_respond(";1;"); + + // Baud rate + for(const struct DECREPTPARM_baud *p = DECREPTPARM_baud_arr; p->baud != -1; p++) { + if (p->baud == sysconf->uart_baudrate) { + apars_respond(p->msg); + apars_respond(";"); + apars_respond(p->msg); + break; + } + } + + // multiplier 1, flags 0 + apars_respond(";1;0x"); // ROM cartridge number ?? + } + break; + + case 'p': + if (leadchar == '!') { // RIS + /* On real VT there are differences between soft and hard reset, we treat both equally */ + screen_reset(); + } else { + warn_bad_csi(); + } + break; + + default: + warn_bad_csi(); + } +} diff --git a/user/apars_csi.h b/user/apars_csi.h new file mode 100644 index 0000000..cbd0a72 --- /dev/null +++ b/user/apars_csi.h @@ -0,0 +1,10 @@ +// +// Created by MightyPork on 2017/08/20. +// + +#ifndef ESP_VT100_FIRMWARE_APARS_CSI_H +#define ESP_VT100_FIRMWARE_APARS_CSI_H + +void apars_handle_csi(char leadchar, const int *params, int count, char keychar); + +#endif //ESP_VT100_FIRMWARE_APARS_CSI_H diff --git a/user/apars_dcs.c b/user/apars_dcs.c new file mode 100644 index 0000000..a454d5e --- /dev/null +++ b/user/apars_dcs.c @@ -0,0 +1,89 @@ +// +// Created by MightyPork on 2017/08/20. +// +// Handle DCS sequences (Device Control String) +// DCS Pt ST +// (DCS = ESC P) +// +// Those are used for terminal status queries. +// +// Pt = +// $ q " p .... read conformance level +// $ q " q .... read character protection attribute +// $ q r ...... read scrolling region extents +// $ q s ...... read horizontal margins extents +// $ q m ...... read SGR in a format that would restore them exactly when run as a CSI Pm m +// $ q SP q ... read cursor style +// +// For details, see +// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Device-Control-functions +// + +#include +#include "apars_dcs.h" +#include "ansi_parser_callbacks.h" +#include "screen.h" +#include "apars_logging.h" + +/** + * Helper function to parse incoming DCS (Device Control String) + * @param buffer - the DCS body (after DCS and before ST) + */ +void ICACHE_FLASH_ATTR +apars_handle_dcs(const char *buffer) +{ + 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 + apars_respond("\033P1$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, "\033P1$r%d\"q\033\\", 0); // 0 - Can erase - TODO real protection status if implemented + apars_respond(buf); + } + else if (buffer[2] == 'r') { + // DECSTBM - Query scrolling region + sprintf(buf, "\033P1$r%d;%dr\033\\", 1, termconf_scratch.height); // 1-80 TODO real extent of scrolling region + apars_respond(buf); + } + else if (buffer[2] == 's') { + // DECSLRM - Query horizontal margins + sprintf(buf, "\033P1$r%d;%ds\033\\", 1, termconf_scratch.width); // Can erase - TODO real extent of horiz margins if implemented + apars_respond(buf); + } + else if (buffer[2] == 'm') { + // SGR - query SGR + apars_respond("\033P1$r"); + screen_report_sgr(buf); + apars_respond(buf); + apars_respond("m\033\\"); + } + else if (strneq(buffer+2, " q", 2)) { + // DECSCUSR - Query cursor style + sprintf(buf, "\033P1$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). + */ + apars_respond(buf); + } + else { + // invalid query + ansi_noimpl("DCS %s ST", buffer); + sprintf(buf, "\033P0$r%s\033\\", buffer+2); + } + } + else { + ansi_warn("Bad DCS: %s", buffer); + apars_show_context(); + } +} diff --git a/user/apars_dcs.h b/user/apars_dcs.h new file mode 100644 index 0000000..f9d4ad7 --- /dev/null +++ b/user/apars_dcs.h @@ -0,0 +1,10 @@ +// +// Created by MightyPork on 2017/08/20. +// + +#ifndef ESP_VT100_FIRMWARE_APARS_DCS_H +#define ESP_VT100_FIRMWARE_APARS_DCS_H + +void apars_handle_dcs(const char *buffer); + +#endif //ESP_VT100_FIRMWARE_APARS_DCS_H diff --git a/user/apars_logging.h b/user/apars_logging.h new file mode 100644 index 0000000..a4bd0f4 --- /dev/null +++ b/user/apars_logging.h @@ -0,0 +1,29 @@ +// +// Created by MightyPork on 2017/08/20. +// +// Logging functions for the parser, can be switched off with a compile flag +// + +#ifndef ESP_VT100_FIRMWARE_APARS_LOGGING_H +#define ESP_VT100_FIRMWARE_APARS_LOGGING_H + +#include + +// defined in the makefile +#if DEBUG_ANSI +#define ansi_warn warn +#define ansi_dbg dbg +#else +#define ansi_warn(...) +#define ansi_dbg(...) +#endif + +#if DEBUG_ANSI_NOIMPL +#define ansi_noimpl(fmt, ...) warn("NOIMPL: " fmt, ##__VA_ARGS__) +#define ansi_noimpl_r(fmt, ...) warn(fmt, ##__VA_ARGS__) +#else +#define ansi_noimpl(fmt, ...) +#define ansi_noimpl_r(fmt, ...) +#endif + +#endif //ESP_VT100_FIRMWARE_APARS_LOGGING_H diff --git a/user/apars_osc.c b/user/apars_osc.c new file mode 100644 index 0000000..18f9855 --- /dev/null +++ b/user/apars_osc.c @@ -0,0 +1,65 @@ +// +// Created by MightyPork on 2017/08/20. +// +// Handle OSC commands (Operating System Command) +// ESC ] Ps ; Pt ST +// +// Those are used to pass various text variables to the terminal: +// +// Ps = 0 or 2 ... set screen title to Pt +// Ps = 81-85 ... set button label to Pt +// + +#include "apars_osc.h" +#include "apars_logging.h" +#include "screen.h" +#include "ansi_parser.h" + +/** + * 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) + */ +void ICACHE_FLASH_ATTR +apars_handle_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) { + screen_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_noimpl("OSC %d ; %s ST", n, buffer); + } + } + else { + ansi_warn("BAD OSC: %s", orig_buff); + apars_show_context(); + } +} diff --git a/user/apars_osc.h b/user/apars_osc.h new file mode 100644 index 0000000..cd2c96f --- /dev/null +++ b/user/apars_osc.h @@ -0,0 +1,10 @@ +// +// Created by MightyPork on 2017/08/20. +// + +#ifndef ESP_VT100_FIRMWARE_APARS_OSC_H +#define ESP_VT100_FIRMWARE_APARS_OSC_H + +void apars_handle_osc(const char *buffer); + +#endif //ESP_VT100_FIRMWARE_APARS_OSC_H diff --git a/user/apars_short.c b/user/apars_short.c new file mode 100644 index 0000000..329c40d --- /dev/null +++ b/user/apars_short.c @@ -0,0 +1,179 @@ +// +// Created by MightyPork on 2017/08/20. +// +// Short sequences: (* = not implemented) +// +// ESC char +// ESC 6 Back Index (DECBI), VT420 and up. +// ESC 7 Save Cursor (DECSC). +// ESC 8 Restore Cursor (DECRC). +// ESC 9 Forward Index (DECFI), VT420 and up. +// ESC = Application Keypad (DECKPAM). +// ESC > Normal Keypad (DECKPNM). +// ESC F Cursor to lower left corner of screen. +// ESC c Full Reset (RIS). +// *ESC l Memory Lock (per HP terminals). Locks memory above the cursor. +// *ESC m Memory Unlock (per HP terminals). +// ESC n Invoke the G2 Character Set as GL (LS2). +// ESC o Invoke the G3 Character Set as GL (LS3). +// *ESC | Invoke the G3 Character Set as GR (LS3R). +// *ESC } Invoke the G2 Character Set as GR (LS2R). +// *ESC ~ Invoke the G1 Character Set as GR (LS1R). +// +// ESC # Ps +// *ESC # 3 DEC double-height line, top half (DECDHL). +// *ESC # 4 DEC double-height line, bottom half (DECDHL). +// *ESC # 5 DEC single-width line (DECSWL). +// *ESC # 6 DEC double-width line (DECDWL). +// ESC # 8 DEC Screen Alignment Test (DECALN). +// +// ESC SP char +// *ESC SP F 7-bit controls (S7C1T). +// *ESC SP G 8-bit controls (S8C1T). +// *ESC SP L Set ANSI conformance level 1 (dpANS X3.134.1). +// *ESC SP M Set ANSI conformance level 2 (dpANS X3.134.1). +// *ESC SP N Set ANSI conformance level 3 (dpANS X3.134.1). +// +// Charset commands +// ESC ( char Designate G0 character set ('0' = symbols, 'B' = ASCII-US, 'A' = ASCII-UK) +// ESC ) char Designate G1 character set +// SO Switch to G0 character set +// SI Switch to G1 character set +// + +#include +#include "apars_short.h" +#include "apars_logging.h" +#include "screen.h" + +// ----- Character Set --- + +/** + * Command to assign G0 or G1 + * @param slot - ( or ) for G0 or G1 + * @param c - character table ID (0, B etc) + */ +void ICACHE_FLASH_ATTR +apars_handle_chs_designate(char slot, char c) +{ + if (slot == '(') screen_set_charset(0, c); // G0 + else if (slot == ')') screen_set_charset(1, c); // G1 + else { + ansi_noimpl("ESC %c %c", slot, c); + } + // other alternatives * + . - / not implemented +} + +/** Select charset slot */ +void ICACHE_FLASH_ATTR +apars_handle_chs_switch(int Gx) +{ + screen_set_charset_n(Gx); +} + +// ----- ESC SP char ----- + +/** + * ESC SP (this sets 8/7-bit mode and some other archaic options) + * @param c - key character + */ +void ICACHE_FLASH_ATTR +apars_handle_space_cmd(char c) +{ + ansi_noimpl("ESC SP %c", c); +} + +// ----- ESC # num ----- + +/** + * Codes in the format ESC # n + * @param c - the trailing symbol (numeric ASCII) + */ +void ICACHE_FLASH_ATTR apars_handle_hash_cmd(char c) +{ + switch(c) { + case '3': // Double size, top half + case '4': // Single size, bottom half + case '5': // Single width, single height + case '6': // Double width + ansi_noimpl("Double Size Line"); + break; + + case '8': + screen_fill_with_E(); + break; + + default: + ansi_noimpl("ESC # %c", c); + } +} + +// ----- ESC char ----- + +/** + * Single-character escape codes (ESC x) + * @param c - the trailing symbol (ASCII) + */ +void ICACHE_FLASH_ATTR apars_handle_short_cmd(char c) +{ + switch(c) { + case 'c': // screen reset + screen_reset(); + break; + + case '7': // save cursor + attributes + screen_cursor_save(true); + break; + + case '8': // restore cursor + attributes + screen_cursor_restore(true); + break; + + case 'E': // same as CR LF + screen_cursor_move(1, 0, false); + screen_cursor_set_x(0); + break; + + case 'F': // bottom left + screen_cursor_set(termconf_scratch.height-1, 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': + screen_set_tab(); + break; + + case '>': + screen_set_numpad_alt_mode(false); + break; + + 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_noimpl("ESC %c", c); + } +} diff --git a/user/apars_short.h b/user/apars_short.h new file mode 100644 index 0000000..2616862 --- /dev/null +++ b/user/apars_short.h @@ -0,0 +1,14 @@ +// +// Created by MightyPork on 2017/08/20. +// + +#ifndef ESP_VT100_FIRMWARE_APARS_SIMPLE_H +#define ESP_VT100_FIRMWARE_APARS_SIMPLE_H + +void apars_handle_short_cmd(char c); +void apars_handle_hash_cmd(char c); +void apars_handle_space_cmd(char c); +void apars_handle_chs_designate(char slot, char c); +void apars_handle_chs_switch(int Gx); + +#endif //ESP_VT100_FIRMWARE_APARS_SIMPLE_H diff --git a/user/apars_string.c b/user/apars_string.c new file mode 100644 index 0000000..08de067 --- /dev/null +++ b/user/apars_string.c @@ -0,0 +1,52 @@ +// +// Created by MightyPork on 2017/08/20. +// +// String based commands. +// Those command start with a introducer sequence, followed by arbitrary string, +// and end with a String Terminator (`ESC \`, or `BEL`) +// +// ESC k Pt ST ... set screen title (same as `OSC 0 ; Pt ST`, is shorter) +// ESC ] Pt ST ... OSC - Operating System Command (split to its own file) +// ESC P Pt ST ... DCS - Device Control String (split to its own file) +// ESC ^ Pt ST ... PM - Privacy message (unused) +// ESC _ Pt ST ... APC - Application Program Command (unused) +// ESC X Pt ST ... SOS - Start Of String (unused; sent back in response to ENQ) + +#include +#include "apars_string.h" +#include "apars_logging.h" +#include "ansi_parser_callbacks.h" +#include "screen.h" + +// ----- Generic String cmd - disambiguation ----- + +void ICACHE_FLASH_ATTR +apars_handle_string_cmd(char leadchar, const char *buffer) +{ + switch (leadchar) { + case 'k': // ESC k TITLE ST (defined in GNU screen manpage) + screen_set_title(buffer); + break; + + case ']': // OSC - Operating System Command + apars_handle_osc(buffer); + break; + + case 'P': // DCS - Device Control String + apars_handle_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("Bad str cmd"); + apars_show_context(); + } +} diff --git a/user/apars_string.h b/user/apars_string.h new file mode 100644 index 0000000..1305905 --- /dev/null +++ b/user/apars_string.h @@ -0,0 +1,10 @@ +// +// Created by MightyPork on 2017/08/20. +// + +#ifndef ESP_VT100_FIRMWARE_APARS_STRING_H +#define ESP_VT100_FIRMWARE_APARS_STRING_H + +void apars_handle_string_cmd(char leadchar, const char *buffer); + +#endif //ESP_VT100_FIRMWARE_APARS_STRING_H diff --git a/user/apars_utf8.c b/user/apars_utf8.c new file mode 100644 index 0000000..a01a16b --- /dev/null +++ b/user/apars_utf8.c @@ -0,0 +1,84 @@ +// +// Created by MightyPork on 2017/08/20. +// +// UTF-8 parser - collects bytes of a code point before writing them +// into a screen cell. +// + +#include "apars_utf8.h" +#include "apars_logging.h" +#include "screen.h" + +static char utf_collect[4]; +static int utf_i = 0; +static int utf_j = 0; + +/** + * 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) +{ + utf_i = 0; + utf_j = 0; + memset(utf_collect, 0, 4); +} + +/** + * Handle a received plain character + * @param c - received character + */ +void ICACHE_FLASH_ATTR +apars_handle_plainchar(char c) +{ + // collecting unicode glyphs... + if (c & 0x80) { + if (utf_i == 0) { + // start + if (c == 192 || c == 193 || c >= 245) { + // forbidden codes + goto fail; + } + + if ((c & 0xE0) == 0xC0) { + utf_i = 2; + } + else if ((c & 0xF0) == 0xE0) { + utf_i = 3; + } + else if ((c & 0xF8) == 0xF0) { + utf_i = 4; + } + else { + // chars over 127 that don't start unicode sequences + goto fail; + } + + utf_collect[0] = c; + utf_j = 1; + } + else { + if ((c & 0xC0) != 0x80) { + goto fail; + } + else { + utf_collect[utf_j++] = c; + if (utf_j >= utf_i) { + screen_putchar(utf_collect); + apars_reset_utf8buffer(); + } + } + } + } + else { + utf_collect[0] = c; + utf_collect[1] = 0; // just to make sure it's closed... + screen_putchar(utf_collect); + } + + return; + fail: + ansi_warn("Bad UTF-8: %0Xh", c); + apars_reset_utf8buffer(); +} diff --git a/user/apars_utf8.h b/user/apars_utf8.h new file mode 100644 index 0000000..06b0a85 --- /dev/null +++ b/user/apars_utf8.h @@ -0,0 +1,11 @@ +// +// Created by MightyPork on 2017/08/20. +// + +#ifndef ESP_VT100_FIRMWARE_APARS_UTF8_H +#define ESP_VT100_FIRMWARE_APARS_UTF8_H + +void apars_handle_plainchar(char c); +void apars_reset_utf8buffer(void); + +#endif //ESP_VT100_FIRMWARE_APARS_UTF8_H diff --git a/user/cgi_main.c b/user/cgi_main.c index 3c03368..32a885d 100644 --- a/user/cgi_main.c +++ b/user/cgi_main.c @@ -5,7 +5,7 @@ #include "cgi_main.h" #include "screen.h" -#include "user_main.h" +#include "version.h" #include "helpers.h" /** diff --git a/user/screen.c b/user/screen.c index 83e6bc3..ea5c2de 100644 --- a/user/screen.c +++ b/user/screen.c @@ -566,6 +566,13 @@ screen_resize(int rows, int cols) NOTIFY_DONE(); } +void ICACHE_FLASH_ATTR +screen_set_title(const char *title) +{ + strncpy(termconf_scratch.title, title, TERM_TITLE_LEN); + screen_notifyChange(CHANGE_LABELS); +} + /** * Shift screen upwards */ @@ -626,6 +633,13 @@ void ICACHE_FLASH_ATTR screen_set_scrolling_region(int from, int to) { // TODO implement (also add to scr) + if (from == 0 && to == 0) { + // whole screen + } else if (from > 1 && from < H-1 && to > from) { + // set range + } else { + // Bad range, do nothing + } } //endregion diff --git a/user/screen.h b/user/screen.h index 4f4d75e..b918e24 100644 --- a/user/screen.h +++ b/user/screen.h @@ -85,6 +85,10 @@ void terminal_apply_settings_noclear(void); void screen_init(void); /** Change the screen size */ void screen_resize(int rows, int cols); +/** Set screen title */ +void screen_set_title(const char *title); +/** Set a button text */ +void screen_set_button_text(int num, const char *text); // --- Encoding --- diff --git a/user/user_main.c b/user/user_main.c index 248db2a..c11ec8b 100644 --- a/user/user_main.c +++ b/user/user_main.c @@ -23,7 +23,7 @@ #include "io.h" #include "screen.h" #include "routes.h" -#include "user_main.h" +#include "version.h" #include "uart_driver.h" #include "ansi_parser_callbacks.h" #include "wifimgr.h" diff --git a/user/user_main.h b/user/version.h similarity index 68% rename from user/user_main.h rename to user/version.h index 8067be5..8343948 100644 --- a/user/user_main.h +++ b/user/version.h @@ -1,5 +1,9 @@ -#ifndef USER_MAIN_H_H -#define USER_MAIN_H_H +// +// Created by MightyPork on 2017/08/20. +// + +#ifndef ESP_VT100_FIRMWARE_VERSION_H +#define ESP_VT100_FIRMWARE_VERSION_H #define FW_V_MAJOR 0 #define FW_V_MINOR 6 @@ -9,4 +13,4 @@ #define FIRMWARE_VERSION_NUM (FW_V_MAJOR*10000 + FW_V_MINOR*100 + FW_V_PATCH) // this is used in ID queries #define TERMINAL_GITHUB_REPO "https://github.com/MightyPork/esp-vt100-firmware" -#endif //USER_MAIN_H_H +#endif //ESP_VT100_FIRMWARE_VERSION_H