#include "render.h" #include "terminal.h" #include "utils.h" #include "zf_log.h" #include #include #include #include #include #include // usleep #include // Does this copy the terminal? Render::Render(int fd, Terminal &term) : term(term) { this->fd = fd; reset(); } void Render::reset(void) { speed = 0; effect = 0; overlimit = 0; image_data = nullptr; // FUTURE: vector image_size = 0; // FUTURE: vector } void Render::set_image(const char **lines, int size) { image_data = lines; image_size = size; } void Render::reset_image(void) { image_data = nullptr; image_size = 0; } void Render::sleep(void) { if (overlimit) return; if (speed) { // * 100 still too slow. ms_sleep(speed * 10); } } int Render::ms_sleep(unsigned int ms) { int result = 0; struct timespec ts = {ms / 1000, (ms % 1000) * 1000000L}; do { struct timespec ts_sleep = ts; result = nanosleep(&ts_sleep, &ts); } while ((-1 == result)); return result; } void Render::color(int color) { const int MYSTIC[] = {0, 4, 2, 6, 1, 5, 3, 7}; std::ostringstream oss; switch (color) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: oss << "\x1b[0;" << MYSTIC[color] + 30 << "m"; break; case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: oss << "\x1b[0;1;" << MYSTIC[color - 8] + 30 << "m"; break; case 16: case 17: case 18: case 19: case 20: case 21: case 22: case 23: oss << "\x1b[" << MYSTIC[color - 16] + 40 << "m"; break; case 24: case 25: case 26: case 27: case 28: case 29: case 30: case 31: oss << "\x1b[5;" << MYSTIC[color - 24] + 40 << "m"; break; default: break; } std::string buffer = oss.str(); ZF_LOGD("write_color( %d ): %s", color, repr(buffer.c_str())); write(fd, buffer.c_str(), buffer.size()); } void Render::pos_goto(int x, int y) { std::ostringstream oss; oss << "\x1b[" << y << ";" << x << "H"; std::string buffer = oss.str(); write(fd, buffer.c_str(), buffer.size()); }; void Render::send(char ch) { // render_effect // int effect = effect; int l; char space = ' '; char bs = '\b'; Terminal::termchar tc; tc = term.putchar(ch); if (tc.in_ansi) { // We are inside ANSI command, so write(fd, &ch, 1); return; } switch (effect) { case 1: // CHAR + SPC + BS render_sleep(); write(fd, &ch, 1); render_sleep(); write(fd, &space, 1); render_sleep(); render_sleep(); write(fd, &bs, 1); break; case 2: // CHAR + 8 spaces + 8 BS // This might be too much. #define MOVE 4 render_sleep(); write(fd, &ch, 1); for (l = 0; l < MOVE; l++) { render_sleep(); write(fd, &space, 1); } for (l = 0; l < MOVE; l++) { render_sleep(); write(fd, &bs, 1); } break; case 0: default: // NORMAL render_sleep(); write(fd, &ch, 1); break; } } void Render::send(std::string &string_out) { // render time_t start = time(NULL); size_t pos = 0; int elapsed; size_t tpos; while ((tpos = string_out.find(TRIGGER, pos)) != std::string::npos) { while (pos != tpos) { elapsed = time(NULL) - start; if (elapsed > SLEEP_LIMIT) { overlimit = 1; speed = 0; } send(string_out[pos]); pos++; } pos += strlen(TRIGGER); process_trigger(string_out, pos); } while (pos < string_out.size()) { elapsed = time(NULL) - start; if (elapsed > SLEEP_LIMIT) { overlimit = 1; speed = 0; } send(string_out[pos]); pos++; } } int Render::digit(std::string str, size_t &pos, int digits) { int i = 0; while (digits > 0) { if (std::isdigit(str[pos])) { i *= 10; i += str[pos] - '0'; pos++; } else { ZF_LOGE("digit fail"); } digits--; }; return i; } void Render::process_trigger(std::string str, size_t &pos) { char ch = str[pos]; int i, x, y; pos++; switch (ch) { case 'D': i = digit(str, pos, 2); if ((i > 0) && (i < 80)) { ZF_LOGI("DEL %02d", i); for (x = 0; x < i; x++) { write(fd, "\b \b", 3); term.putstring("\b \b"); } }; break; case 'C': { i = 0; if (str[pos] == 'S') { pos++; color_history.push_back(term.color_restore()); // console_history.push_back(console); ZF_LOGI("saved color [%s].", term.color_restore().c_str()); break; } if (str[pos] == 'R') { pos++; if (color_history.empty()) { ZF_LOGE("Trying to Color Restore from empty vector!"); break; } std::string restore = color_history.back(); color_history.pop_back(); ZF_LOGI("restored color [%s].", restore.c_str()); write(fd, restore.c_str(), restore.size()); break; } i = digit(str, pos, 2); write_color(fd, i); } break; // no longer takes a filename. :) case 'F': case 'f': { int UsePos = 0; if (ch == 'f') { UsePos = 1; x = digit(str, pos, 2); y = digit(str, pos, 2); ZF_LOGI("file at (%d, %d)", x, y); } ZF_LOGD("IMAGE (%d lines)", image_size); if (UsePos) { send_image(x, y); } else { send_image(); }; } break; case 'G': { x = digit(str, pos, 2); y = digit(str, pos, 2); char buffer[20]; // row ; column H int slen; ZF_LOGD("GOTO (%d,%d)", x, y); slen = snprintf(buffer, sizeof(buffer), "\x1b[%d;%dH", y, x); if (slen >= (int)sizeof(buffer)) { ZF_LOGE("snprintf %d > size %d", slen, (int)sizeof(buffer)); buffer[0] = 0; } write(fd, buffer, strlen(buffer)); } break; case 'R': { i = digit(str, pos); if ((i >= 0) && (i < 10)) { ZF_LOGI("RENDER %d", i); effect = i; } else { effect = 0; } } break; case 'S': { i = digit(str, pos); if ((i >= 0) && (i < 10)) { ZF_LOGI("SPEED %d", i); speed = i; } else { speed = 0; } } break; case 'P': { i = digit(str, pos); if ((i >= 0) && (i < 10)) { ZF_LOGI("PAWS %d", i); // sleep(i); if (!overlimit) { // sleep(i); ms_sleep(i * 1000); }; } } break; } } int Render::send_image(int x, int y) { int i; const char **lines = image_data; if (lines == nullptr) { ZF_LOGE("No image data for send_image(%d,%d)", x, y); return 0; } for (i = 0; i < image_size; i++) { send_goto(fd, x, y); y++; write(fd, lines[i], strlen(lines[i])); } // success reset_image(); return 1; } int Render::send_image(void) { int i; const char **lines = image_data; if (lines == nullptr) { ZF_LOGE("No image data for send_image."); return 0; } for (i = 0; i < image_size; i++) { std::string line(lines[i]); line.append("\r\n"); // rendering the ^BAT^ was a bad idea. (uses ^ chars). // render(fd, line); write(fd, lines[i], strlen(lines[i])); write(fd, "\r\n", 2); } // success reset_image(); return 1; } extern const char *strnstr(const char *source, int len, const char *needle); extern struct console_details console; struct render current_render; int render_overlimit = 0; void reset_render(void) { current_render.speed = 0; current_render.effect = 0; render_overlimit = 0; } // or possibly a vector, and pop the image pointer off // allowing for multiple images. const char **image_data = NULL; int image_size = 0; void render_image(const char **lines, int size) { image_data = lines; image_size = size; } void reset_image(void) { image_data = NULL; image_size = 0; } int ms_sleep(unsigned int ms) { int result = 0; struct timespec ts = {ms / 1000, (ms % 1000) * 1000000L}; do { struct timespec ts_sleep = ts; result = nanosleep(&ts_sleep, &ts); } while ((-1 == result)); return result; } void render_sleep(void) { if (render_overlimit) return; if (current_render.speed) { // * 100 still too slow. ms_sleep(current_render.speed * 10); } } /* Well SNAP! Mystic numbers don't remotely match ANSI color codes. 00 : Sets the current foreground to Black 0;30 01 : Sets the current foreground to Dark Blue 0;34 02 : Sets the current foreground to Dark Green 0;32 03 : Sets the current foreground to Dark Cyan 0;36 04 : Sets the current foreground to Dark Red 0;31 05 : Sets the current foreground to Dark Magenta 0;35 06 : Sets the current foreground to Brown 0;33 07 : Sets the current foreground to Grey 0;37 08 : Sets the current foreground to Dark Grey 1;30 09 : Sets the current foreground to Light Blue 1;34 10 : Sets the current foreground to Light Green 1;32 11 : Sets the current foreground to Light Cyan 1;36 12 : Sets the current foreground to Light Red 1;31 13 : Sets the current foreground to Light Magenta 1;35 14 : Sets the current foreground to Yellow 1;33 15 : Sets the current foreground to White 1;37 16 : Sets the current background to Black 40 17 : Sets the current background to Blue 44 18 : Sets the current background to Green 42 19 : Sets the current background to Cyan 46 20 : Sets the current background to Red 41 21 : Sets the current background to Magenta 45 22 : Sets the current background to Brown 43 23 : Sets the current background to Grey 47 24 : Sets the current background to black with blinking foreground 5;40 25 : Sets the current background to blue with blinking foreground 5;44 26 : Sets the current background to green with blinking foreground 5;42 27 : Sets the current background to cyan with blinking foreground 5;46 28 : Sets the current background to red with blinking foreground 5;41 29 : Sets the current background to magenta with blinking foreground 5;45 30 : Sets the current background to brown with blinking foreground 5;43 31 : Sets the current background to grey with blinking foreground 5;47 Other things that Mystic does ... [A## - Move the cursor up ## lines [B## - Move the cursor down ## lines [C## - Move the cursor forward (to the right) ## columns [D## - Move the cursor backwards (to the left) ## columns [K - Clear from the current cursor position to the end of the line [L - Move cursor and erase data backwards from current column to column ## [X## - Move cursor to X coordinate ## [Y## - Move cursor to Y coordinate ## BS - Sends 1 destructive backspace sequence (ASCII 8-32-8) CL - Clears the screen (ANSI 1,1 locate and [2J or ASCII 12) CR - Send a carrage return and line feed (move to next line) RA - Restore the saved text attribute color RS - Restore the saved user's terminal screen SA - Save the current text attribute color SS - Save the entire user's terminal screen */ // Covert MYSTIC color to (Proper) ANSI COLOR. const int MYSTIC[] = {0, 4, 2, 6, 1, 5, 3, 7}; // ANSI_color = MYSTIC[ odd_mystic_color % 8 ] void write_color(int fd, int color) { char buffer[12]; int slen; switch (color) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: slen = snprintf(buffer, sizeof(buffer), "\x1b[0;3%dm", MYSTIC[color]); if (slen >= (int)sizeof(buffer)) { ZF_LOGE("snprintf %d > size %d", slen, (int)sizeof(buffer)); buffer[0] = 0; } break; case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: slen = snprintf(buffer, sizeof(buffer), "\x1b[0;1;3%dm", MYSTIC[color - 8]); if (slen >= (int)sizeof(buffer)) { ZF_LOGE("snprintf %d > size %d", slen, (int)sizeof(buffer)); buffer[0] = 0; } break; case 16: case 17: case 18: case 19: case 20: case 21: case 22: case 23: slen = snprintf(buffer, sizeof(buffer), "\x1b[4%dm", MYSTIC[color - 16]); if (slen >= (int)sizeof(buffer)) { ZF_LOGE("snprintf %d > size %d", slen, (int)sizeof(buffer)); buffer[0] = 0; } break; case 24: case 25: case 26: case 27: case 28: case 29: case 30: case 31: slen = snprintf(buffer, sizeof(buffer), "\x1b[5;4%dm", MYSTIC[color - 24]); if (slen >= (int)sizeof(buffer)) { ZF_LOGE("snprintf %d > size %d", slen, (int)sizeof(buffer)); buffer[0] = 0; } break; default: buffer[0] = 0; break; } ZF_LOGD("write_color( %d ): %s", color, repr(buffer)); write(fd, buffer, strlen(buffer)); } void send_goto(int fd, int x, int y) { char gbuffer[16]; int slen; slen = snprintf(gbuffer, sizeof(gbuffer), "\x1b[%d;%dH", y, x); if (slen >= (int)sizeof(gbuffer)) { ZF_LOGE("snprintf %d > size %d", slen, (int)sizeof(gbuffer)); gbuffer[0] = 0; } write(fd, gbuffer, strlen(gbuffer)); } int send_image(int fd) { int i; const char **lines = image_data; if (lines == NULL) { ZF_LOGE("No image data for send_image."); return 0; } for (i = 0; i < image_size; i++) { std::string line(lines[i]); line.append("\r\n"); // rendering the ^BAT^ was a bad idea. (uses ^ chars). // render(fd, line); write(fd, lines[i], strlen(lines[i])); write(fd, "\r\n", 2); } // success reset_image(); return 1; } int send_image(int fd, int x, int y) { int i; const char **lines = image_data; if (lines == NULL) { ZF_LOGE("No image data for send_image(%d,%d)", x, y); return 0; } for (i = 0; i < image_size; i++) { send_goto(fd, x, y); y++; write(fd, lines[i], strlen(lines[i])); } // success reset_image(); return 1; } int digit(std::string str, size_t &pos, int digits = 1) { int i = 0; while (digits > 0) { if (std::isdigit(str[pos])) { i *= 10; i += str[pos] - '0'; pos++; } else { ZF_LOGE("digit fail"); } digits--; }; return i; } static std::vector console_history; /** * process_trigger( fd, str, &pos ) * * This process a command trigger. * It has seen TRIGGER, and now it is * processing whatever comes after it. * It will perform the process, and * update pos to whatever is next. */ void process_trigger(int fd, std::string str, size_t &pos) { char ch = str[pos]; int i, x, y; pos++; switch (ch) { case 'D': i = digit(str, pos, 2); if ((i > 0) && (i < 80)) { ZF_LOGI("DEL %02d", i); for (x = 0; x < i; x++) { write(fd, "\b \b", 3); console_receive(&console, "\b \b"); } }; break; case 'C': { i = 0; if (str[pos] == 'S') { pos++; console_history.push_back(console); ZF_LOGI("saved color [%d/%d/%d].", console.fgcolor, console.bgcolor, console.status); break; } if (str[pos] == 'R') { pos++; if (console_history.empty()) { ZF_LOGE("Trying to Color Restore from empty vector!"); break; } console_details old_color = console_history.back(); console_history.pop_back(); const char *restore = color_restore(&old_color); ZF_LOGI("restore color [%d/%d/%d] %s", old_color.fgcolor, old_color.bgcolor, old_color.status, repr(restore)); write(fd, restore, strlen(restore)); break; } i = digit(str, pos, 2); write_color(fd, i); } break; // no longer takes a filename. :) case 'F': case 'f': { int UsePos = 0; if (ch == 'f') { UsePos = 1; x = digit(str, pos, 2); y = digit(str, pos, 2); ZF_LOGI("file at (%d, %d)", x, y); } ZF_LOGD("IMAGE (%d lines)", image_size); if (UsePos) { send_image(fd, x, y); } else { send_image(fd); }; } break; case 'G': { x = digit(str, pos, 2); y = digit(str, pos, 2); char buffer[20]; // row ; column H int slen; ZF_LOGD("GOTO (%d,%d)", x, y); slen = snprintf(buffer, sizeof(buffer), "\x1b[%d;%dH", y, x); if (slen >= (int)sizeof(buffer)) { ZF_LOGE("snprintf %d > size %d", slen, (int)sizeof(buffer)); buffer[0] = 0; } write(fd, buffer, strlen(buffer)); } break; case 'R': { i = digit(str, pos); if ((i > 0) && (i < 10)) { ZF_LOGI("RENDER %d", i); current_render.effect = i; } else { current_render.effect = 0; } } break; case 'S': { i = digit(str, pos); if ((i > 0) && (i < 10)) { ZF_LOGI("SPEED %d", i); current_render.speed = i; } else { current_render.speed = 0; } } break; case 'P': { i = digit(str, pos); if ((i > 0) && (i < 10)) { ZF_LOGI("PAWS %d", i); // sleep(i); if (!render_overlimit) { sleep(i); }; } } break; } } /** * render_effect( fd, ch ) * * Displays the given character with whatever * rendering effect is currently active. * (If any). */ void render_effect(int fd, char ch) { int effect = current_render.effect; int l; char space = ' '; char bs = '\b'; termchar tc; tc = console_char(&console, ch); if (tc.in_ansi) { // We are inside ANSI command, so write(fd, &ch, 1); return; } switch (effect) { case 1: // CHAR + SPC + BS render_sleep(); write(fd, &ch, 1); render_sleep(); write(fd, &space, 1); render_sleep(); render_sleep(); write(fd, &bs, 1); break; case 2: // CHAR + 8 spaces + 8 BS // This might be too much. #define MOVE 4 render_sleep(); write(fd, &ch, 1); for (l = 0; l < MOVE; l++) { render_sleep(); write(fd, &space, 1); } for (l = 0; l < MOVE; l++) { render_sleep(); write(fd, &bs, 1); } break; case 0: default: // NORMAL render_sleep(); write(fd, &ch, 1); break; } } /** * render( fd, string_out ) * * Render an entire string. * Handles TRIGGER. * Renders with effects. */ void render(int fd, std::string &string_out) { // reset_render(); time_t start = time(NULL); size_t pos = 0; int elapsed; size_t tpos; while ((tpos = string_out.find(TRIGGER, pos)) != std::string::npos) { while (pos != tpos) { elapsed = time(NULL) - start; if (elapsed > SLEEP_LIMIT) { render_overlimit = 1; current_render.speed = 0; } render_effect(fd, string_out[pos]); pos++; } pos += strlen(TRIGGER); process_trigger(fd, string_out, pos); } while (pos < string_out.size()) { elapsed = time(NULL) - start; if (elapsed > SLEEP_LIMIT) { render_overlimit = 1; current_render.speed = 0; } render_effect(fd, string_out[pos]); pos++; } }