#include #include #include #include #include // usleep #include "render.h" #include "terminal.h" #include "zf_log.h" extern const char *strnstr(const char *source, int len, const char *needle); extern const char *repr(const char *data); 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 >= 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 >= 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 >= 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 >= 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 >= 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++) { 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 send_file(int fd, char *filename) { FILE *fp; char buffer[100]; int read; fp = fopen(filename, "rb"); if (fp == NULL) { ZF_LOGD("Failed to open %s", filename); return 0; } while ((read = fread(buffer, 1, sizeof(buffer), fp)) > 0) { write(fd, buffer, read); }; fclose(fp); return 1; } int send_file(int fd, int x, int y, char *filename) { FILE *fp; char buffer[100]; int read; fp = fopen(filename, "rb"); if (fp == NULL) { ZF_LOGD("Failed to open %s", filename); return 0; } send_goto(fd, x, y); y++; while ((read = fread(buffer, 1, sizeof(buffer), fp)) > 0) { char *cp, *last_cp; buffer[read] = 0; last_cp = buffer; while ((cp = strchr(last_cp, '\n')) != NULL) { *cp = 0; write(fd, last_cp, strlen(last_cp)); send_goto(fd, x, y); y++; last_cp = cp + 1; }; write(fd, last_cp, strlen(last_cp)); }; fclose(fp); 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; } /** * 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); } }; break; case 'C': { i = 0; if (str[pos] == 'R') { pos++; const char *restore = color_restore(&console); 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 >= 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; } } /** * process_trigger( fd, *cp ) * * This process a command trigger. * It has seen TRIGGER, and now it is * processing whatever comes after it. * It will perform the process, and * return the char * of whatever is next. */ const char *process_trigger(int fd, const char *cp) { char ch; int i, x, y; ch = *cp; cp++; switch (ch) { case 'D': i = 0; if (isdigit(*cp)) { i = (*cp) - '0'; cp++; }; if (isdigit(*cp)) { i *= 10; i += (*cp) - '0'; cp++; }; if ((i > 0) && (i < 80)) { ZF_LOGI("DEL %02d", i); for (x = 0; x < i; x++) { write(fd, "\b \b", 3); } }; break; case 'C': { i = 0; if (*cp == 'R') { cp++; const char *restore = color_restore(&console); write(fd, restore, strlen(restore)); break; } if (isdigit(*cp)) { i = (*cp) - '0'; cp++; }; if (isdigit(*cp)) { i *= 10; i += (*cp) - '0'; cp++; }; write_color(fd, i); } break; // no longer takes a filename. :) case 'F': case 'f': { int pos = 0; if (ch == 'f') { pos = 1; x = (*cp) - '0'; cp++; x *= 10; x += (*cp) - '0'; cp++; y = (*cp) - '0'; cp++; y *= 10; y += (*cp) - '0'; cp++; ZF_LOGI("file at (%d, %d)", x, y); } ZF_LOGD("IMAGE (%d lines)", image_size); if (pos) { send_image(fd, x, y); } else { send_image(fd); }; } break; case 'G': { x = 0; if (isdigit(*cp)) { x = (*cp) - '0'; cp++; }; if (isdigit(*cp)) { x *= 10; x += (*cp) - '0'; cp++; }; y = 0; if (isdigit(*cp)) { y = (*cp) - '0'; cp++; }; if (isdigit(*cp)) { y *= 10; y += (*cp) - '0'; cp++; }; 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 >= sizeof(buffer)) { ZF_LOGE("snprintf %d > size %d", slen, (int)sizeof(buffer)); buffer[0] = 0; } write(fd, buffer, strlen(buffer)); } break; case 'R': { i = 0; if (isdigit(*cp)) { i = (*cp) - '0'; cp++; }; if ((i > 0) && (i < 10)) { ZF_LOGI("RENDER %d", i); current_render.effect = i; } else { current_render.effect = 0; } } break; case 'S': { i = 0; if (isdigit(*cp)) { i = (*cp) - '0'; cp++; }; if ((i > 0) && (i < 10)) { ZF_LOGI("SPEED %d", i); current_render.speed = i; } else { current_render.speed = 0; } } break; case 'P': { i = 0; if (isdigit(*cp)) { i = (*cp) - '0'; cp++; }; if ((i > 0) && (i < 10)) { ZF_LOGI("PAWS %d", i); // sleep(i); if (!render_overlimit) { sleep(i); }; } } break; } return cp; } /** * 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'; 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 render_sleep(); write(fd, &ch, 1); for (l = 0; l < 8; l++) { render_sleep(); write(fd, &space, 1); } for (l = 0; l < 8; l++) { render_sleep(); write(fd, &bs, 1); } break; case 0: default: // NORMAL render_sleep(); write(fd, &ch, 1); break; } } #ifdef UNWANTED /** * render( fd, string_out ) * * Render an entire string. * Handles TRIGGER. * Renders with effects. */ void render(int fd, const char *string_out, int len) { const char *cp = string_out; const char *trigger = cp; time_t start = time(NULL); int elapsed; int over = 0; reset_render(); // ZF_LOGV("render(%d, %s)", fd, repr(string_out)); ZF_LOGV_MEM(string_out, len, "render(%d, %d bytes):", fd, len); // Check our time from time to time. // If we start running long, disable sleeps. while ((trigger = strnstr(cp, len - (cp - string_out), TRIGGER)) != NULL) { // There is special things to handle in here. while (cp != trigger) { elapsed = time(NULL) - start; if (elapsed > SLEEP_LIMIT) { render_overlimit = 1; current_render.speed = 0; }; // write(fd, cp, 1 ); render_effect(fd, *cp); cp++; }; // ZF_LOGI( "at trigger: (%s)", cp); cp += strlen(TRIGGER); // Ok, we're pointing at the trigger -- do something. cp = process_trigger(fd, cp); // ZF_LOGI( "after trigger: (%s)", cp); }; // We still might be under a rendering effect. while (cp < (string_out + len)) { elapsed = time(NULL) - start; if (elapsed > SLEEP_LIMIT) { render_overlimit = 1; current_render.speed = 0; }; // write(fd, cp, 1); render_effect(fd, *cp); cp++; } } #endif /** * 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++; } }