123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853 |
- /*
- 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 "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(); }
- Terminal::Terminal(const Terminal &old)
- : saved_cursor_position(old.saved_cursor_position) {
- posx = old.posx;
- posy = old.posy;
- in_ansi = old.in_ansi;
- fgcolor = old.fgcolor;
- bgcolor = old.bgcolor;
- status = old.status;
- ansi = old.ansi;
- }
- Terminal &Terminal::operator=(Terminal &rhs) {
- posx = rhs.posx;
- posy = rhs.posy;
- in_ansi = rhs.in_ansi;
- fgcolor = rhs.fgcolor;
- bgcolor = rhs.bgcolor;
- status = rhs.status;
- ansi = rhs.ansi;
- saved_cursor_position = rhs.saved_cursor_position;
- return *this;
- }
- 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();
- this->dcs_map = 0x0b;
- }
- int Terminal::getx(void) { return posx; }
- int Terminal::gety(void) { return posy; }
- int Terminal::getstatus(void) { return status; }
- int Terminal::inANSI(void) { return in_ansi; }
- int Terminal::fg(void) { return fgcolor; }
- int Terminal::bg(void) { return bgcolor; }
- bool Terminal::ansiempty(void) { return ansi.empty(); }
- int Terminal::dcs(void) { return dcs_map; }
- 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 == last) {
- // Missing first and last
- number = 1;
- number2 = 1;
- } else {
- 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);
- // DCS (Designate Character Set)
- if (ansi[1] == '(') {
- if (ansi.size() == 3) {
- // End DCS
- switch (ansi[2]) {
- case '0':
- dcs_map = 0;
- break;
- case 'B':
- dcs_map = 0x0b;
- break;
- default:
- ZF_LOGE("Unknown DCS Mode ^ESC(%c Selected!", ansi[2]);
- // Use our default, but we know it's wrong!
- dcs_map = 0x0b;
- break;
- }
- this->in_ansi = 0;
- this->ansi.clear();
- tc.in_ansi = 1;
- tc.ansi = DCS;
- return tc;
- }
- }
- 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++ ways
- 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;
- }
- /*
- * Given the ansi number codes
- * Figure out fg, bg and status.
- */
- void ansi_color(struct console_details *cdp, int color) {
- // There are many calls to this.
- // 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);
- }
|