#include "terminal.h" #include "utils.h" #include #include #include #include #include #include #include #include #include #include // random() #include #include #include // strcasecmp #include #include #include #include #include /* `Tip the DoorMan` DoorMan is a BBS door manager that handles translating UTF-8 to CP437 for using ncurses based programs as BBS doors. We might start with using the quite excellent iconv library for our conversions, but I believe I'll eventually switch to using a map of some sort to do the process. (Calling a library over and over again to convert the same chars to the same thing over and over again just sounds "wrong".) -NOXLATE (No Translation, leave UTF-8 as-is) -ICONV (Use ICONV) (Default will be using our own table.) We will also need to convert the user input (which will be a BBS Terminal program) into whatever Linux expects from users. Function keys, arrow keys, etc. -NOTERM (Don't translate the terminal input) Logging will be a huge requirement to figure much of this out! -NOLOG (Disable logging) We'll want to optionally capture/eat Ctrl-C, because most programs quit on that. -NOC (No Ctrl-C, not the default) Anything not matching here is considered to be the program + arguments. */ /* "FEATURES" We don't understand color code 96/97. (Bright white/bright cyan). Maybe try mapping those back to the original non-bright colors? There's a possibility of missing keys (if they aren't all received in one "packet"). ? Unicode double lines are definitely missing! Yikes! Anything else that exists in unicode that maps over to high ASCII. https://en.wikipedia.org/wiki/Code_page_437 I'm not sure, but it almost looks like it has the CP437 char and then the unicode char below it. That might be exactly what I need! https://jrgraphix.net/r/Unicode/2500-257F http://xahlee.info/comp/unicode_drawing_shapes.html Still need to finish up function keys. still having issues with chars (mc, pageup/pagedown -- garbled chars, that looks like missing unicode multi-byte issues) */ enum TRANSLATE { NONE, ICONV, INTERNAL } translate = NONE; /* https://softwareengineering.stackexchange.com/questions/141973/how-do-you-achieve-a-numeric-versioning-scheme-with-git Use tags to mark commits with version numbers: git tag -a v2.5 -m 'Version 2.5' Push tags upstream—this is not done by default: git push --tags Then use the describe command: git describe --tags --long */ std::string version = "0.0.0"; // DMVERSION; // #include // handle Ctrl-C/SIGINT /* Log level guideline: * - ZF_LOG_FATAL - happened something impossible and absolutely unexpected. * Process can't continue and must be terminated. * Example: division by zero, unexpected modifications from other thread. * - ZF_LOG_ERROR - happened something possible, but highly unexpected. The * process is able to recover and continue execution. * Example: out of memory (could also be FATAL if not handled properly). * - ZF_LOG_WARN - happened something that *usually* should not happen and * significantly changes application behavior for some period of time. * Example: configuration file not found, auth error. * - ZF_LOG_INFO - happened significant life cycle event or major state * transition. * Example: app started, user logged in. * - ZF_LOG_DEBUG - minimal set of events that could help to reconstruct the * execution path. Usually disabled in release builds. * - ZF_LOG_VERBOSE - all other events. Usually disabled in release builds. * * *Ideally*, log file of debugged, well tested, production ready application * should be empty or very small. Choosing a right log level is as important as * providing short and self descriptive log message. */ /* #define ZF_LOG_VERBOSE 1 #define ZF_LOG_DEBUG 2 #define ZF_LOG_INFO 3 #define ZF_LOG_WARN 4 #define ZF_LOG_ERROR 5 #define ZF_LOG_FATAL 6 */ // When debugging low-level, use this: // #define ZF_LOG_LEVEL ZF_LOG_VERBOSE // Except this doesn't work. It needs to be anywere the // zf_log.h is included. // LOGGING with file output #include "zf_log.h" FILE *g_log_file; static void file_output_callback(const zf_log_message *msg, void *arg) { (void)arg; *msg->p = '\n'; fwrite(msg->buf, msg->p - msg->buf + 1, 1, g_log_file); fflush(g_log_file); } static void file_output_close(void) { fclose(g_log_file); } static int file_output_open(const char *const log_path) { g_log_file = fopen(log_path, "a"); if (!g_log_file) { ZF_LOGW("Failed to open log file %s", log_path); return 0; } atexit(file_output_close); zf_log_set_output_v(ZF_LOG_PUT_STD, 0, file_output_callback); return 1; } void log_flush(void) { fflush(g_log_file); } // END LOGGING /* This is done. :D My buffering system works with stack'em. TO FIX: Stop using c strings, must use char * buffer + int length. MAY CONTAIN NULL VALUES. Rework some things here. Here's the "plan": if buffer is EMPTY: time_idle = 1; // setup for "random timeout value mess" // we're in luck! The last parameter is time interval/timeout. :D timeout.tv_sec = 10; // randrange(10-25) timeout.tv_usec = 0; NOT EMPTY: // we're in luck! The last parameter is time interval/timeout. :D timeout.tv_sec = 0; timeout.tv_usec = 10; // Wild Guess Here? Maybe higher, maybe lower? time_idle = 0; ON READ: read/append to current buffer. We can't use nulls -- what if they are using ZModem, there's nulls in the file! Look for trailing / the very last "\r\n". (I could mangle/chunk it line by line. But I'm not sure I'd need to do that.) Optional "mangle" buffer up to that very point -- and send up to that point. Option #2: Maybe we send everything if program has been running for under 20 seconds. This would allow the ANSI detect to not get screwed up by this new idea. ON TIMEOUT: if time_idle: Activate funny harry timeout events. else: Ok, we *STILL* haven't received any more characters into the buffer -- even after waiting. (Maybe we haven't waited long enough?) send the pending information in the buffer and clear it out. Maybe this is a prompt, and there won't be a \r\n. This allows for cleaner process of "lines" of buffer. We shouldn't break in the midDLE OF A WORD. Downside is that we sit on buffer contents a little while / some amount of time -- which will add some lag to prompts showing up. (LAG? Are you kidding?) ZModem: start: "rz^M**"... 05-12 18:12:15.916 >> rz^M**^XB00000000000000^M<8A>^Q 05-12 18:12:15.928 << **\x18B0100000023be50\r\n\x11 05-12 18:12:15.928 >> *^XC^D 05-12 18:12:15.939 << **\x18B0900000000a87c\r\n\x11 05-12 18:12:15.940 >> *^XC # Start of PK zipfile. 05-12 18:12:15.941 >> PK^C^D^T end: 05-12 18:26:38.700 << **\x18B0100000023be50\r\n\x11 05-12 18:26:38.700 >> **^XB0823a77600344c^M<8A> 05-12 18:26:38.711 << **\x18B0800000000022d\r\n 05-12 18:26:38.712 >> OO^MESC[0m */ // TODO: Get everything above this -- into another file. /* void display_line(const char *line) { char input[1024]; char output[1024]; strcpy(input, line); converter.convert(input, output, sizeof(output)); printf("%s\n", output); } */ void terminal_output(TRANSLATE xlate, std::string &buffer) { /* The current problem is this. The unichoad characters are getting spilt by buffer breaks. */ static Terminal term; // static IConv converter("UTF-8", "CP437"); // static IConv converter("CP437", "UTF-8"); // TO, FROM ZF_LOGV_MEM(buffer.data(), buffer.size(), "Buffer now:"); ZF_LOGD("Buffer: %s", repr(buffer.c_str())); if (xlate == NONE) { // no translation write(STDOUT_FILENO, buffer.data(), buffer.size()); buffer.clear(); } else { std::string ansi; int dcs_mode = term.dcs(); // The Hunt for Red Unichoad // https://en.wikipedia.org/wiki/UTF-8 /* Number of bytes First code point Last code point Byte 1 Byte 2 Byte 3 Byte 4 1 U+0000 U+007F 0xxxxxxx 2 U+0080 U+07FF 110xxxxx 10xxxxxx 3 U+0800 U+FFFF 1110xxxx 10xxxxxx 10xxxxxx 4 U+10000 U+10FFFF[18] 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ int length = buffer.size(); int save = 0; // default to saving zero characters. char uchoad; // 1 char searches: if (length >= 1) { uchoad = buffer[length - 1]; if (unicode_len(uchoad)) save = 1; } if (length >= 2) { uchoad = buffer[length - 2]; if (unicode_len(uchoad) > 2) save = 2; } // We'll probably never see any of these, but it's here if we need it. if (length >= 3) { uchoad = buffer[length - 3]; if (unicode_len(uchoad) > 3) save = 3; } std::string saved; if (save) { ZF_LOGE("Saving %d chars", save); ZF_LOGV_MEM(buffer.data(), buffer.size(), "PRE-SAVE:"); saved = buffer.substr(length - save, save); buffer = buffer.substr(0, length - save); ZF_LOGV_MEM(buffer.data(), buffer.size(), "After Save:"); ZF_LOGV_MEM(saved.data(), saved.size(), "SAVED:"); } // TO FIX: Pull chars out, and look up in the map. Don't brute force your // way through all of the times. static std::map utf8cp437 = { {"\xe2\x98\xba", 1}, {"\xe2\x98\xbb", 2}, {"\xe2\x99\xa5", 3}, {"\xe2\x99\xa6", 4}, {"\xe2\x99\xa3", 5}, {"\xe2\x99\xa0", 6}, // {"\xe2\x80\xa2", 7}, {"\xe2\x97\x98", 8}, {"\xe2\x97\x8b", 9}, //{"\xe2\x97\x99", 0x0a}, {"\xe2\x99\x82", 0x0b}, {"\xe2\x99\x80", 0x0c}, //{"\xe2\x99\xaa", 0x0d}, {"\xe2\x99\xab", 0x0e}, {"\xe2\x98\xbc", 0x0f}, {"\xe2\x96\xba", 0x10}, {"\xe2\x97\x84", 0x11}, {"\xe2\x86\x95", 0x12}, {"\xe2\x80\xbc", 0x13}, {"\xc2\xb6", 0x14}, {"\xc2\xa7", 0x15}, {"\xe2\x96\xac", 0x16}, {"\xe2\x86\xa8", 0x17}, {"\xe2\x86\x91", 0x18}, {"\xe2\x86\x93", 0x19}, {"\xe2\x86\x92", 0x1a}, {"\xe2\x86\x90", 0x1b}, {"\xe2\x88\x9f", 0x1c}, {"\xe2\x86\x94", 0x1d}, {"\xe2\x96\xb2", 0x1e}, {"\xe2\x96\xbc", 0x1f}, {"\xe2\x8c\x82", 0x7f}, {"\xc3\x87", 0x80}, {"\xc3\xbc", 0x81}, {"\xc3\xa9", 0x82}, {"\xc3\xa2", 0x83}, {"\xc3\xa4", 0x84}, {"\xc3\xa0", 0x85}, {"\xc3\xa5", 0x86}, {"\xc3\xa7", 0x87}, {"\xc3\xaa", 0x88}, {"\xc3\xab", 0x89}, {"\xc3\xa8", 0x8a}, {"\xc3\xaf", 0x8b}, {"\xc3\xae", 0x8c}, {"\xc3\xac", 0x8d}, {"\xc3\x84", 0x8e}, {"\xc3\x85", 0x8f}, {"\xc3\x89", 0x90}, {"\xc3\xa6", 0x91}, {"\xc3\x86", 0x92}, {"\xc3\xb4", 0x93}, {"\xc3\xb6", 0x94}, {"\xc3\xb2", 0x95}, {"\xc3\xbb", 0x96}, {"\xc3\xb9", 0x97}, {"\xc3\xbf", 0x98}, {"\xc3\x96", 0x99}, {"\xc3\x9c", 0x9a}, {"\xc2\xa2", 0x9b}, {"\xc2\xa3", 0x9c}, {"\xc2\xa5", 0x9d}, {"\xe2\x82\xa7", 0x9e}, {"\xc6\x92", 0x9f}, {"\xc3\xa1", 0xa0}, {"\xc3\xad", 0xa1}, {"\xc3\xb3", 0xa2}, {"\xc3\xba", 0xa3}, {"\xc3\xb1", 0xa4}, {"\xc3\x91", 0xa5}, {"\xc2\xaa", 0xa6}, {"\xc2\xba", 0xa7}, {"\xc2\xbf", 0xa8}, {"\xe2\x8c\x90", 0xa9}, {"\xc2\xac", 0xaa}, {"\xc2\xbd", 0xab}, {"\xc2\xbc", 0xac}, {"\xc2\xa1", 0xad}, {"\xc2\xab", 0xae}, {"\xc2\xbb", 0xaf}, {"\xe2\x96\x91", 0xb0}, {"\xe2\x96\x92", 0xb1}, {"\xe2\x96\x93", 0xb2}, {"\xe2\x94\x82", 0xb3}, {"\xe2\x94\xa4", 0xb4}, {"\xe2\x95\xa1", 0xb5}, {"\xe2\x95\xa2", 0xb6}, {"\xe2\x95\x96", 0xb7}, {"\xe2\x95\x95", 0xb8}, {"\xe2\x95\xa3", 0xb9}, {"\xe2\x95\x91", 0xba}, {"\xe2\x95\x97", 0xbb}, {"\xe2\x95\x9d", 0xbc}, {"\xe2\x95\x9c", 0xbd}, {"\xe2\x95\x9b", 0xbe}, {"\xe2\x94\x90", 0xbf}, {"\xe2\x94\x94", 0xc0}, {"\xe2\x94\xb4", 0xc1}, {"\xe2\x94\xac", 0xc2}, {"\xe2\x94\x9c", 0xc3}, {"\xe2\x94\x80", 0xc4}, {"\xe2\x94\xbc", 0xc5}, {"\xe2\x95\x9e", 0xc6}, {"\xe2\x95\x9f", 0xc7}, {"\xe2\x95\x9a", 0xc8}, {"\xe2\x95\x94", 0xc9}, {"\xe2\x95\xa9", 0xca}, {"\xe2\x95\xa6", 0xcb}, {"\xe2\x95\xa0", 0xcc}, {"\xe2\x95\x90", 0xcd}, {"\xe2\x95\xac", 0xce}, {"\xe2\x95\xa7", 0xcf}, {"\xe2\x95\xa8", 0xd0}, {"\xe2\x95\xa4", 0xd1}, {"\xe2\x95\xa5", 0xd2}, {"\xe2\x95\x99", 0xd3}, {"\xe2\x95\x98", 0xd4}, {"\xe2\x95\x92", 0xd5}, {"\xe2\x95\x93", 0xd6}, {"\xe2\x95\xab", 0xd7}, {"\xe2\x95\xaa", 0xd8}, {"\xe2\x94\x98", 0xd9}, {"\xe2\x94\x8c", 0xda}, {"\xe2\x96\x88", 0xdb}, {"\xe2\x96\x84", 0xdc}, {"\xe2\x96\x8c", 0xdd}, {"\xe2\x96\x90", 0xde}, {"\xe2\x96\x80", 0xdf}, {"\xce\xb1", 0xe0}, {"\xc3\x9f", 0xe1}, {"\xce\x93", 0xe2}, {"\xcf\x80", 0xe3}, {"\xce\xa3", 0xe4}, {"\xcf\x83", 0xe5}, {"\xc2\xb5", 0xe6}, {"\xcf\x84", 0xe7}, {"\xce\xa6", 0xe8}, {"\xce\x98", 0xe9}, {"\xce\xa9", 0xea}, {"\xce\xb4", 0xeb}, {"\xe2\x88\x9e", 0xec}, {"\xcf\x86", 0xed}, {"\xce\xb5", 0xee}, {"\xe2\x88\xa9", 0xef}, {"\xe2\x89\xa1", 0xf0}, {"\xc2\xb1", 0xf1}, {"\xe2\x89\xa5", 0xf2}, {"\xe2\x89\xa4", 0xf3}, {"\xe2\x8c\xa0", 0xf4}, {"\xe2\x8c\xa1", 0xf5}, {"\xc3\xb7", 0xf6}, {"\xe2\x89\x88", 0xf7}, {"\xc2\xb0", 0xf8}, {"\xe2\x88\x99", 0xf9}, {"\xc2\xb7", 0xfa}, {"\xe2\x88\x9a", 0xfb}, {"\xe2\x81\xbf", 0xfc}, {"\xc2\xb2", 0xfd}, {"\xe2\x96\xa0", 0xfe}, {"\xc2\xa0", 0xff}}; int c = 0; size_t pos; std::vector keys = {'\xc2', '\xc3', '\xce', '\xcf', '\xe2'}; for (auto ikey = keys.begin(); ikey != keys.end(); ++ikey) { size_t spos = 0; while ((pos = buffer.find(*ikey, spos)) != std::string::npos) { char uchoad = buffer[pos]; int ulen = unicode_len(uchoad); std::string ustr = buffer.substr(pos, ulen); ZF_LOGE_MEM(ustr.data(), ustr.length(), "unicode %d : (@%ld)", ulen, pos); auto upos = utf8cp437.find(ustr.c_str()); if (upos != utf8cp437.end()) { std::string to(1, (char)upos->second); while (replace(buffer, ustr, to)) { c++; } spos = 0; } else { ZF_LOGE("failed to locate."); spos = pos + 1; } } } if (c) { ZF_LOGE("vector/map Replaced %d", c); ZF_LOGV_MEM(buffer.data(), buffer.size(), "After Replace:"); } /* if ((buffer.find('\xe2') != std::string::npos) || (buffer.find('\xc2') != std::string::npos) || (buffer.find('\xc3') != std::string::npos) || (buffer.find('\xce') != std::string::npos) || (buffer.find('\xcf') != std::string::npos)) { c = 0; for (auto it = utf8cp437.begin(); it != utf8cp437.end(); ++it) { while (replace(buffer, it->first, std::string(1, char(it->second)))) { c++; } } if (c) { ZF_LOGE("Replaced %d", c); ZF_LOGV_MEM(buffer.data(), buffer.size(), "After Replace:"); } } */ // Convert bright to bold. while (replace(buffer, "\x1b[90m", "\x1b[1;30m")) { }; while (replace(buffer, "\x1b[91m", "\x1b[1;31m")) { }; while (replace(buffer, "\x1b[92m", "\x1b[1;32m")) { }; while (replace(buffer, "\x1b[93m", "\x1b[1;33m")) { }; while (replace(buffer, "\x1b[94m", "\x1b[1;34m")) { }; while (replace(buffer, "\x1b[95m", "\x1b[1;35m")) { }; while (replace(buffer, "\x1b[96m", "\x1b[1;36m")) { }; while (replace(buffer, "\x1b[97m", "\x1b[1;37m")) { }; // Search for DCS mode, and translate the line characters. for (auto iter = buffer.begin(); iter != buffer.end(); ++iter) { char c = *iter; Terminal::termchar tc = term.putchar(c); if (tc.in_ansi) { ansi.append(1, c); if (tc.ansi == Terminal::ANSI_TYPE::START) continue; // Ok, the ansi command is completed. if (tc.ansi == Terminal::ANSI_TYPE::DCS) { // Terminal::ANSI_TYPE::DCS) { // Ok, EAT this command! it's worthless to the terminal we're talking // to. dcs_mode = term.dcs(); } } else { // Ok, we're not in any ANSI mode. /* ┌ ─ ┬ ┐ │ │ │ │ ├ ─ ┼ ┤ └ ─ ┴ ┘ We cheat here. Instead of converting to unicode, we convert to CP437 chars. */ if (dcs_mode == 0) { switch (c) { case 'j': c = '\xd9'; // '┘'; break; case 'k': c = '\xbf'; // '┐'; break; case 'l': c = '\xda'; // '┌'; break; case 'm': c = '\xc0'; // '└'; break; case 'n': c = '\xc5'; // '┼'; break; case 'q': c = '\xc4'; // '─'; break; case 't': c = '\xc3'; // '├'; break; case 'u': c = '\xb4'; // '┤'; break; case 'v': c = '\xc1'; // '┴'; break; case 'w': c = '\xc2'; // '┬'; break; case 'x': c = '\xb3'; // '│'; break; default: ZF_LOGE("DCS(0): I don't have translation for [%c]", c); } ZF_LOGE("DCS(0) Converted [%c] to [%c]", *iter, c); *iter = c; } } } write(STDOUT_FILENO, buffer.data(), buffer.size()); buffer.clear(); buffer.insert(0, saved); saved.clear(); } } void help(void) { printf("Usage:\n"); printf("Translate (default to internal)\n"); printf("\t-NOXLATE\tNo translation\n"); printf("\t-ICONV\tUse ICONV library\n"); printf("\t-NOLOG\tNo logging\n"); printf("\t-NOC\tDon't allow Ctrl-C\n"); printf("\t-NOTERM\tDon't Translate Keys\n"); printf("\t-LOGTIME\tAdd Hours/Minutes to logfile name\n"); } int main(int argc, char *argv[]) { int master; pid_t pid; // init_harry(); srandom(time(NULL)); /* -NOXLATE (No Translation, leave UTF-8 as-is) -ICONV (Use ICONV) -NOTERM (Don't translate the terminal input) -NOLOG (Disable logging) -NOC (No Ctrl-C, not the default) */ translate = ICONV; // Default to internal translation map (well, not initially.) int CATCH_CTRLC = 0; int NO_LOGGING = 0; int TERM_XLATE = 1; int LOG_TIME = 0; int x; for (x = 1; x < argc; x++) { if (strcasecmp("-NOXLATE", argv[x]) == 0) { translate = NONE; continue; } if (strcasecmp("-ICONV", argv[x]) == 0) { translate = ICONV; continue; } if (strcasecmp("-NOC", argv[x]) == 0) { CATCH_CTRLC = 1; continue; } if (strcasecmp("-NOLOG", argv[x]) == 0) { NO_LOGGING = 1; continue; } if (strcasecmp("-NOTERM", argv[x]) == 0) { TERM_XLATE = 0; continue; } if (strcasecmp("-LOGTIME", argv[x]) == 0) { LOG_TIME = 1; continue; } if ((strcasecmp("-H", argv[x]) == 0) || (strcmp("-?", argv[x]) == 0)) { // display help information. help(); return 0; } if (argv[x][0] == '-') { printf("I don't understand arg %s\n", argv[x]); help(); return 0; } break; } if (x == argc) { printf("No command to run found.\n"); help(); return 0; } std::string logfile; { std::ostringstream buffer; time_t now = time(NULL); struct tm *tmp; tmp = gmtime(&now); // tmp->tm_mon buffer << "doorman-" << tmp->tm_year + 1900 << "-" << std::setfill('0') << std::setw(2) << tmp->tm_mon + 1 << "-" << std::setfill('0') << std::setw(2) << tmp->tm_mday; if (LOG_TIME) { buffer << "-" << std::setw(2) << tmp->tm_hour << std::setw(2) << tmp->tm_min; } buffer << ".log"; logfile = buffer.str(); } if (NO_LOGGING) { zf_log_set_output_level(ZF_LOG_NONE); } else { if (!file_output_open((const char *)logfile.c_str())) return 2; }; ZF_LOGE("DoorMan %s", version.c_str()); // Build the new command to run here char *args[20]; // max 20 args char *target_exec = argv[x]; ZF_LOGE("Target: (%d) [%s]", x, target_exec); // build new args list args[0] = target_exec; // We have the target, skip to the next arg if (x < argc) ++x; int ax = 1; for (; x < argc; x++) { args[ax] = argv[x]; ZF_LOGD("ARG %d : %s", ax, args[ax]); ax++; }; // null term the list args[ax] = NULL; pid = forkpty(&master, NULL, NULL, NULL); // impossible to fork if (pid < 0) { return 1; } // child else if (pid == 0) { // This has been done up above. /* char *args[20]; // max 20 args int x; char new_exec[] = TARGET; // build new args list args[0] = new_exec; for (x = 1; x < argc; x++) { args[x] = argv[x]; }; // null term the list args[x] = NULL; */ // run TARGET, run! /* for (x = 0; args[x] != nullptr; x++) { ZF_LOGD("%d : %s", x, args[x]); } */ execvp(target_exec, args); } // parent else { struct termios tios, orig1; struct timeval timeout; ZF_LOGD("starting"); tcgetattr(master, &tios); tios.c_lflag &= ~(ECHO | ECHONL | ICANON); /* tios.c_iflag &= ~(ICRNL | IXON | BRKINT); tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); tios.c_oflag &= ~(OPOST); */ tcsetattr(master, TCSAFLUSH, &tios); tcgetattr(1, &orig1); tios = orig1; /* tios.c_iflag &= ~(ICRNL | IXON | BRKINT); tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); tios.c_oflag &= ~(OPOST); */ tios.c_iflag &= ~(ICRNL | IXON | ISTRIP | BRKINT); tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); tios.c_oflag &= ~(OPOST); tios.c_cflag |= (CS8); // https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html tcsetattr(1, TCSAFLUSH, &tios); /* This doesn't need to be static -- because it is part of main. Once main ends, we're done. */ #define BSIZE 128 std::string buffer; buffer.reserve(BSIZE); for (;;) { // define estruturas para o select, que serve para verificar qual // se tornou "pronto pra uso" fd_set read_fd; fd_set write_fd; fd_set except_fd; // inicializa as estruturas FD_ZERO(&read_fd); FD_ZERO(&write_fd); FD_ZERO(&except_fd); // atribui o descritor master, obtido pelo forkpty, ao read_fd FD_SET(master, &read_fd); // atribui o stdin ao read_fd FD_SET(STDIN_FILENO, &read_fd); // o descritor tem que ser unico para o programa, a documentacao // recomenda um calculo entre os descritores sendo usados + 1 timeout.tv_sec = 60; timeout.tv_usec = 0; // if (select(master + 1, &read_fd, &write_fd, &except_fd, &timeout) == 0) // { if (select(master + 1, &read_fd, &write_fd, &except_fd, &timeout) == 0) { ZF_LOGI("TIMEOUT"); // This means timeout! } // read_fd esta atribuido com read_fd? if (FD_ISSET(master, &read_fd)) { // leia o que bc esta mandando // ZF_LOGD("read (%d) %d bytes", size, BSIZE - size); char read_buffer[BSIZE + 1]; int total; // We may adjust this later on (adjusting read length). if ((total = read(master, read_buffer, BSIZE)) != -1) { // Ok, we've read more into the buffer. ZF_LOGV("Read %d bytes", total); buffer.append(read_buffer, total); terminal_output(translate, buffer); } else { ZF_LOGE("read = -1, break/exit"); break; } } // read_fd esta atribuido com a entrada padrao? if (FD_ISSET(STDIN_FILENO, &read_fd)) { // leia a entrada padrao char input[BSIZE]; int r = read(STDIN_FILENO, &input, BSIZE); std::string input_str(input, r); // input[r] = 0; // F11 didn't work, it's a "fullscreen" key. static const char *func_keys[12] = {"\x1bOP", "\x1bOQ", "\x1bOR", "\x1bOS", "\x1b[15\x7e", "\x1b[17\x7e", "\x1b[18\x7e", "\x1b[19\x7e", "\x1b[20\x7e", "\x1b[21\x7e", "", "\x1b[24]x7e"}; // left right up down ins del home end PgUp PgDown static const char *dir_keys[] = { "\x1bOD", "\x1bOC", "\x1bOA", "\x1bOB", "\x1b[2\x7e", "\x1b[3\x7e", "\x1bOH", "\x1b[F", "\x1b[5\x7e", "\x1b[6\x7e"}; // e escreva no bc ZF_LOGI_MEM(input_str.data(), input_str.size(), "INPUT <<"); if (CATCH_CTRLC) { remove_all(input_str, '\x03'); } if (TERM_XLATE) { // We're going to try it without any buffer or understanding of ANSI // codes. int c = 0; while (replace(input_str, "\x1b[A", dir_keys[2])) { // OA c++; } while (replace(input_str, "\x1b[B", dir_keys[3])) { // OB c++; } while (replace(input_str, "\x1b[C", dir_keys[1])) { // OC c++; } while (replace(input_str, "\x1b[D", dir_keys[0])) { // OD c++; } while (replace(input_str, "\x1b[U", dir_keys[9])) { // [6~ c++; } while (replace(input_str, "\x1b[V", dir_keys[8])) { // [5~ c++; } while (replace(input_str, "\x1b[H", dir_keys[6])) { // OH c++; } while (replace(input_str, "\x1b[K", dir_keys[7])) { // OF c++; } while (replace(input_str, "\x1b[@", dir_keys[4])) { // [2~ c++; } /* F1-F5 are "broken". They all map to this. while (replace(input_str, "\x1b[1", "\x1bOR")) { // F? c++; } */ while (replace(input_str, "\r\n", "\r")) { c++; } // NON-Doorways, DEL = 0x7f // Terminal DEL = "\x1b[3\7e" while (replace(input_str, std::string("\x7f"), dir_keys[5])) { c++; } // DOORWAYS mode: // Future Fix: Find char(0) pos, get next char, look those 2 chars up // in a map. if (input_str.find(char(0)) != std::string::npos) { // We found a null character, TIAS ZF_LOGE("null found. Start doorways replace mode."); using namespace std::string_literals; // F1-F12 while (replace(input_str, std::string("\x00\x3b"s), std::string(func_keys[0]))) { c++; } while (replace(input_str, std::string("\x00\x3c"s), func_keys[1])) { c++; } while (replace(input_str, std::string("\x00\x3d"s), func_keys[2])) { c++; } while (replace(input_str, std::string("\x00\x3e"s), func_keys[3])) { c++; } while (replace(input_str, std::string("\x00\x3f"s), func_keys[4])) { c++; } while (replace(input_str, std::string("\x00\x40"s), func_keys[5])) { c++; } while (replace(input_str, std::string("\x00\x41"s), func_keys[6])) { c++; } while (replace(input_str, std::string("\x00\x42"s), func_keys[7])) { c++; } while (replace(input_str, std::string("\x00\x43"s), func_keys[8])) { c++; } while (replace(input_str, std::string("\x00\x44"s), func_keys[9])) { c++; } /* while (replace(input_str, std::string("\x00\x85"s), func_keys[10])) { // F11 c++; } */ while ( replace(input_str, std::string("\x00\x86"s), func_keys[11])) { c++; } // Delete = 0x7f (not here) // Left, Right, Up, Down, Insert, Home, End, PgUp, PgDwn while (replace(input_str, std::string("\x00\x4b"s), dir_keys[0])) { // Left c++; } while (replace(input_str, std::string("\x00\x4d"s), dir_keys[1])) { // Right c++; } while (replace(input_str, std::string("\x00\x48"s), dir_keys[2])) { // Up c++; } while (replace(input_str, std::string("\x00\x50"s), dir_keys[3])) { // Down c++; } while (replace(input_str, std::string("\x00\x52"s), dir_keys[4])) { // Ins c++; } // Delete is 0x7f (no null) while (replace(input_str, std::string("\x00\x47"s), dir_keys[6])) { // Home c++; } while (replace(input_str, std::string("\x00\x4f"s), dir_keys[7])) { // End c++; } while (replace(input_str, std::string("\x00\x49"s), dir_keys[8])) { // Page Up c++; } while (replace(input_str, std::string("\x00\x51"s), dir_keys[9])) { // Page Down c++; } if (c) { ZF_LOGE_MEM(input_str.data(), input_str.size(), "Input (%d changed):", c); } } ZF_LOGD_MEM(input_str.data(), input_str.size(), "Write Input String:"); // write(master, &input, r); write(master, input_str.data(), input_str.size()); // This is INPUT from the USER // ZF_LOGI_MEM( input, strlen(input), "<< "); } } } // Ok, we get to EXIT, but the ... program is still open/running. Why? // I think this is a netcat problem. It can't tell that the program has // been closed, so it just flips out. // FIXED with pipexec /* close(master); close(STDIN_FILENO); close(STDOUT_FILENO); int wstate; int rc = waitpid(pid, &wstate, 0); ZF_LOGE("waitpid %d (%d) = %d", rc, pid, wstate); */ // Restore terminal tcsetattr(1, TCSAFLUSH, &orig1); ZF_LOGD("exit"); } return 0; }