/* Terminal tracking Actually, I believe I only really need to track the color information. Everything else, I'm not sure I really care about. (NNY!) */ #include #include // snprintf #include #include #include "terminal.h" #include "utils.h" #include "zf_log.h" void console_init(struct console_details *cdp) { cdp->posx = 0; cdp->posy = 0; cdp->savedx = 0; cdp->savedy = 0; cdp->in_ansi = 0; cdp->fgcolor = 7; cdp->bgcolor = 0; cdp->status = 0; } void ansi_color(struct console_details *cdp, int color) { // ZF_LOGV("ansi_color(%d)", color); if (color == 0) { cdp->status = 0; cdp->fgcolor = 7; cdp->bgcolor = 0; return; } if (color == 1) { cdp->status = 1; return; } if (color == 5) { cdp->status = 5; return; } if ((color >= 30) && (color <= 37)) { cdp->fgcolor = color - 30; return; } if ((color >= 40) && (color <= 47)) { cdp->bgcolor = color - 40; return; } ZF_LOGD("ansi_color( %d ) is unknown to me.", color); } const char *color_restore(struct console_details *cdp) { static char buffer[30]; int slen; if (cdp->status == 0) { slen = snprintf(buffer, sizeof(buffer), "\x1b[0;3%d;4%dm", cdp->fgcolor, cdp->bgcolor); if (slen >= sizeof(buffer)) { ZF_LOGE("snprintf %d > size %d", slen, (int)sizeof(buffer)); buffer[0] = 0; } } else { slen = snprintf(buffer, sizeof(buffer), "\x1b[0;%d;3%d;4%dm", cdp->status, cdp->fgcolor, cdp->bgcolor); if (slen >= sizeof(buffer)) { ZF_LOGE("snprintf %d > size %d", slen, (int)sizeof(buffer)); buffer[0] = 0; } }; return buffer; } ANSI_TYPE console_ansi(struct console_details *cdp, const char *ansi) { int understood = 0; const char *cp = ansi; const char *last = ansi + strlen(ansi) - 1; int number, number2; if (*cp == '\x1b') { cp++; // Ok, that's expected. if (*cp == '[') { cp++; // https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences switch (*last) { case 'A': // cursor up if (cp == last) { number = 1; } else { number = atoi(cp); if (number < 1) { ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi), number); number = 1; } }; cdp->posy -= number; if (cdp->posy < 0) { cdp->posy = 0; ZF_LOGD( "console_ansi( %s ): attempt to move above top of screen (%d)", repr(ansi), number); } understood = 1; return CURSOR; case 'B': // cursor down if (cp == last) { number = 1; } else { number = atoi(cp); if (number < 1) { ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi), number); number = 1; } }; cdp->posy += number; // check range/"scroll" understood = 1; return CURSOR; case 'C': // cursor forward if (cp == last) { number = 1; } else { number = atoi(cp); if (number < 1) { ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi), number); number = 1; } }; cdp->posx += number; // Well. According to the "spec", the screen limits are hard // If the cursor is already at the edge of the screen, this has no // effect. ^ This is *NOT* how any ANSI BBS terminal program acts! while (cdp->posx > 79) { cdp->posy++; // check range/"scroll" cdp->posx -= 79; } understood = 1; return CURSOR; case 'D': // cursor backwards if (cp == last) { number = 1; } else { number = atoi(cp); if (number < 1) { ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi), number); number = 0; } }; cdp->posx -= number; // Well. According to the "spec", the screen limits are hard // If the cursor is already at the edge of the screen, this has no // effect. ^ This is *NOT* how any ANSI BBS terminal program acts! while (cdp->posx < 0) { cdp->posy--; if (cdp->posy < 0) { cdp->posy = 0; } cdp->posx += 79; } understood = 1; return CURSOR; case 'H': // cursor position if (*cp == ';') { // Missing first number number = 1; cp++; if (cp == last) { // missing 2nd number as well? number2 = 1; } else { number2 = atoi(cp); } } else { // Ok, find the first number number = atoi(cp); cp = strchr(cp, ';'); if (cp == NULL) { // Missing 2nd number number2 = 1; } else { // 2nd number found, maybe. cp++; if (cp == last) { number2 = 1; } else { number2 = atoi(cp); } } } // Our positions start at zero, not one. cdp->posx = number - 1; cdp->posy = number2 - 1; understood = 1; return CURSOR; case 'J': // clear if (cp == last) { number = 0; } else { number = atoi(cp); }; // clears ... part of the screen. if (number == 2) { cdp->posx = 0; cdp->posy = 0; }; understood = 1; return CLEAR; case 'm': // color if (cp == last) { // nothing given, default to 0. number = 0; ansi_color(cdp, number); } else { while (cp != last) { number = atoi(cp); ansi_color(cdp, number); cp++; while ((cp != last) && (isdigit(*cp))) { cp++; }; if (cp != last) { if (*cp == ';') { cp++; } } } } understood = 1; return COLOR; case 't': case 'r': case 'h': case '!': // These are ones that I don't care about. case 'n': // This is terminal detect -- give me cursor position understood = 1; return OTHER; default: // unsure -- possibly not important ZF_LOGD("console_ansi( %s ): ???", repr(ansi)); understood = 0; return OTHER; } } } } /* * console_char() * return whether or not we are still in_ansi */ termchar console_char(struct console_details *cdp, char ch) { char *cp; termchar tc; if (cdp->in_ansi) { // Ok, append this char cp = cdp->ansi + strlen(cdp->ansi); *cp = ch; cp++; *cp = 0; if (isalpha(ch)) { // Ok! tc.ansi = console_ansi(cdp, cdp->ansi); cdp->in_ansi = 0; cdp->ansi[0] = 0; tc.in_ansi = 1; return tc; } tc.ansi = START; tc.in_ansi = 1; return tc; } else { tc.ansi = START; tc.in_ansi = 0; if (ch == '\x1b') { cp = cdp->ansi; *cp = ch; cp++; *cp = 0; cdp->in_ansi = 1; tc.in_ansi = 1; return tc; } // should I try reporting MOTION non-ANSI ? if (ch == '\r') { // Carriage return cdp->posx = 0; return tc; } if (ch == '\n') { cdp->posy++; // check range/"scroll" return tc; } if (ch == '\b') { // Backspace. if (cdp->posx > 0) { cdp->posx--; } return tc; } if (ch == '\f') { // form feed // treat as clear screen cdp->posx = 0; cdp->posy = 0; return tc; } /* I don't believe that anything else can possibly be here, other then an actual printable character. So! */ cdp->posx++; if (cdp->posx > 79) { cdp->posx = 0; cdp->posy++; // check range/"scroll" } return tc; } } #ifdef UNWANTED void console_string(struct console_details *cdp, const char *chars) { int x; for (x = 0; x < strlen(chars); x++) { console_char(cdp, chars[x]); } } #endif // extern void log_flush(void); void console_receive(struct console_details *cdp, std::string chars) { ZF_LOGI("console_char %lu chars", chars.size()); /* // the c way for (int x = 0; x < chars.size(); x++) { console_char(cdp, chars[x]); } */ // the C++ way for (auto strit = chars.begin(); strit != chars.end(); strit++) console_char(cdp, *strit); } #ifdef UNWANTED void console_receive(struct console_details *cdp, const char *chars, int len) { int x; for (x = 0; x < len; x++) { console_char(cdp, chars[x]); } } #endif