| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788 | /*Terminal trackingActually, I believe I only really need to track the color information.Everything else, I'm not sure I really care about.   (NNY!) */#include "terminal.h"#include "utils.h"#include "zf_log.h"#include <ctype.h>#include <sstream>#include <stdio.h> // snprintf#include <stdlib.h>#include <string.h>#include <vector>Terminal::Terminal() { this->init(); }void Terminal::init(void) {  this->posx = this->posy = 0;  this->in_ansi = 0;  this->fgcolor = 7;  this->bgcolor = 0;  this->status = 0;  this->saved_cursor_position.clear();}void Terminal::ansi_color(int color) {  // ZF_LOGV("ansi_color(%d)", color);  if (color == 0) {    this->status = 0;    this->fgcolor = 7;    this->bgcolor = 0;    return;  }  if ((color == 1) || (color == 2) || (color == 3) || (color == 4) ||      (color == 5)) {    this->status = color;    return;  }  if ((color >= 30) && (color <= 37)) {    this->fgcolor = color - 30;    return;  }  if ((color >= 40) && (color <= 47)) {    this->bgcolor = color - 40;    return;  }  if (color == 39) {    // default fg color    this->fgcolor = 7;    return;  }  if (color == 49) {    // default bg color    this->bgcolor = 0;    return;  }  ZF_LOGD("ansi_color( %d ) is unknown to me.", color);}std::string Terminal::color_restore(void) {  std::ostringstream oss;  // possible optimize:  If fg is 7, don't set (0 reset does that), if bg is 0,  // don't set (0 does that)  if (this->status == 0) {    oss << "\x1b[0;" << this->fgcolor + 30 << ";" << this->bgcolor + 40 << "m";  } else {    oss << "\x1b[0;" << this->status << ";" << this->fgcolor + 30 << ";"        << this->bgcolor + 40 << "m";  }  std::string buffer = oss.str();  return buffer;}Terminal::ANSI_TYPE Terminal::ansi_code(std::string ansi) {  std::string::iterator cp = ansi.begin();  std::string::iterator last = ansi.end() - 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 = std::stoi(std::string(cp, last));          if (number < 1) {            ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi.c_str()),                    number);            number = 1;          }        };        this->posy -= number;        if (this->posy < 0) {          this->posy = 0;          ZF_LOGD(              "console_ansi( %s ): attempt to move above top of screen (%d)",              repr(ansi.c_str()), number);        }        return CURSOR;      case 'B':        // cursor down        if (cp == last) {          number = 1;        } else {          number = std::stoi(std::string(cp, last));          if (number < 1) {            ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi.c_str()),                    number);            number = 1;          }        };        this->posy += number;        // check range/"scroll"        return CURSOR;      case 'C':        // cursor forward        if (cp == last) {          number = 1;        } else {          number = std::stoi(std::string(cp, last));          if (number < 1) {            ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi.c_str()),                    number);            number = 1;          }        };        this->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 (this->posx > 79) {          this->posy++;          // check range/"scroll"          this->posx -= 79;        }        return CURSOR;      case 'D':        // cursor backwards        if (cp == last) {          number = 1;        } else {          number = std::stoi(std::string(cp, last));          if (number < 1) {            ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi.c_str()),                    number);            number = 0;          }        };        this->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 (this->posx < 0) {          this->posy--;          if (this->posy < 0) {            this->posy = 0;          }          this->posx += 79;        }        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 = std::stoi(std::string(cp, last));          }        } else {          // Ok, find the first number          number = std::stoi(std::string(cp, last));          // covert iterator to position          std::size_t pos = ansi.find(';', std::distance(ansi.begin(), cp));          if (pos == std::string::npos) {            // Missing 2nd number            number2 = 1;          } else {            // 2nd number found, maybe.            cp = ansi.begin() + pos;            ++cp;            if (cp == last) {              number2 = 1;            } else {              number2 = std::stoi(std::string(cp, last));            }          }        }        // Our positions start at zero, not one.        this->posy = number - 1;        this->posx = number2 - 1;        return CURSOR;      case 'J':        // clear        if (cp == last) {          number = 0;        } else {          number = std::stoi(std::string(cp, last));        };        // clears ... part of the screen.        if (number == 2) {          this->posx = 0;          this->posy = 0;        };        return CLEAR;      case 'K':        // clear line        if (cp == last) {          number = 0;        } else {          number = std::stoi(std::string(cp, last));        };        return CLEAR;      case 'm':        // color        if (cp == last) {          // nothing given, default to 0.          number = 0;          ansi_color(number);        } else {          while (cp != last) {            number = std::stoi(std::string(cp, last));            ansi_color(number);            ++cp;            while ((cp != last) && (isdigit(*cp))) {              ++cp;            };            if (cp != last) {              if (*cp == ';') {                cp++;              }            }          }        }        return COLOR;      case 's':        // save position        saved_cursor_position.push_back(std::make_pair(this->posx, this->posy));        return CURSOR;      case 'u':        // restore position        if (saved_cursor_position.empty()) {          ZF_LOGE("Restore cursor position from empty history.");        } else {          std::pair<int, int> pos = saved_cursor_position.back();          this->posx = pos.first;          this->posy = pos.second;          saved_cursor_position.pop_back();        }        return CURSOR;      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        return OTHER;      default:        // unsure -- possibly not important        ZF_LOGD("console_ansi( %s ): ???", repr(ansi.c_str()));        return OTHER;      }    }  }  ZF_LOGD("console_ansi( %s ) : ???", repr(ansi.c_str()));  return OTHER;}Terminal::termchar Terminal::putchar(char ch) {  Terminal::termchar tc;  if (this->in_ansi) {    // Ok, append this char    this->ansi.append(1, ch);    if (isalpha(ch)) {      // Ok!  end of ANSI code, process it.      tc.ansi = ansi_code(this->ansi);      this->in_ansi = 0;      this->ansi.clear();      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') {      this->ansi.append(1, ch);      this->in_ansi = 1;      tc.in_ansi = 1;      return tc;    }    // should I try reporting MOTION non-ANSI ?    if (ch == '\r') {      // Carriage return      this->posx = 0;      return tc;    }    if (ch == '\n') {      this->posy++;      // check range/"scroll"      return tc;    }    if (ch == '\b') {      // Backspace.      if (this->posx > 0) {        this->posx--;      }      return tc;    }    if (ch == '\f') {      // form feed      // treat as clear screen      this->posx = 0;      this->posy = 0;      return tc;    }    /*      I don't believe that anything else can possibly be here, other then an      actual printable character.  So!      FUTURE: Store the screen text + colors    */    this->posx++;    if (this->posx > 79) {      this->posx = 0;      this->posy++;      // check range/"scroll"    }    return tc;  }}void Terminal::putstring(std::string text) {  // This gets noisy when called from render effect ^D  // ZF_LOGI("console_char %lu chars", chars.size());  for (auto strit = text.begin(); strit != text.end(); strit++)    putchar(*strit);}// Old non-C++ waysvoid 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;}/* * Given the ansi number codes * Figure out fg, bg and status. */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) || (color == 2) || (color == 3) || (color == 4) ||      (color == 5)) {    cdp->status = color;    return;  }  if ((color >= 30) && (color <= 37)) {    cdp->fgcolor = color - 30;    return;  }  if ((color >= 40) && (color <= 47)) {    cdp->bgcolor = color - 40;    return;  }  if (color == 39) {    // default fg color    cdp->fgcolor = 7;    return;  }  if (color == 49) {    // default bg color    cdp->bgcolor = 0;    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;  // possible optimize:  If fg is 7, don't set (0 reset does that), if bg is 0,  // don't set (0 does that)  if (cdp->status == 0) {    slen = snprintf(buffer, sizeof(buffer), "\x1b[0;3%d;4%dm", cdp->fgcolor,                    cdp->bgcolor);    if (slen >= (int)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 >= (int)sizeof(buffer)) {      ZF_LOGE("snprintf %d > size %d", slen, (int)sizeof(buffer));      buffer[0] = 0;    }  };  return buffer;}/* store cursor position X,Y */std::vector<std::pair<int, int>> cursor_position_history;ANSI_TYPE console_ansi(struct console_details *cdp, const char *ansi) {  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);        }        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"        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;        }        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;        }        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->posy = number - 1;        cdp->posx = number2 - 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;        };        return CLEAR;      case 'K':        // clear line        if (cp == last) {          number = 0;        } else {          number = atoi(cp);        };        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++;              }            }          }        }        return COLOR;      case 's':        // save position        cursor_position_history.push_back(std::make_pair(cdp->posx, cdp->posy));        return CURSOR;      case 'u':        // restore position        if (cursor_position_history.empty()) {          ZF_LOGE("Restore cursor position from empty history.");        } else {          std::pair<int, int> pos = cursor_position_history.back();          cdp->posx = pos.first;          cdp->posy = pos.second;          cursor_position_history.pop_back();        }        return CURSOR;      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        return OTHER;      default:        // unsure -- possibly not important        ZF_LOGD("console_ansi( %s ): ???", repr(ansi));        return OTHER;      }    }  }  ZF_LOGD("console_ansi( %s ) : ???", repr(ansi));  return OTHER;}/* * console_char() *  return whether or not we are still in_ansi * * in_ansi TRUE: START (receiving start of/fragment of ANSI) *               CURSOR, COLOR, CLEAR, OTHER *               Results of the last ANSI parse. * in_ansi FALSE: START Normal character. * */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!  end of ANSI code, process it.      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!      FUTURE: Store the screen text + colors    */    cdp->posx++;    if (cdp->posx > 79) {      cdp->posx = 0;      cdp->posy++;      // check range/"scroll"    }    return tc;  }}void console_receive(struct console_details *cdp, std::string chars) {  // This gets noisy when called from render effect ^D  // ZF_LOGI("console_char %lu chars", chars.size());  for (auto strit = chars.begin(); strit != chars.end(); strit++)    console_char(cdp, *strit);}
 |