#include #include #include #include #include // usleep #include "render.h" #include "terminal.h" #include "zf_log.h" #include "utils.h" 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; } 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]; switch (color) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: sprintf(buffer, "\x1b[0;3%dm", MYSTIC[color]); break; case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: sprintf(buffer, "\x1b[0;1;3%dm", MYSTIC[color - 8]); break; case 16: case 17: case 18: case 19: case 20: case 21: case 22: case 23: sprintf(buffer, "\x1b[4%dm", MYSTIC[color - 16]); break; case 24: case 25: case 26: case 27: case 28: case 29: case 30: case 31: sprintf(buffer, "\x1b[5;4%dm", MYSTIC[color - 24]); break; default: buffer[0] = 0; break; } ZF_LOGD("write_color( %d ): %s", color, repr(buffer)); write(fd, buffer, strlen(buffer)); } 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; } void send_goto(int fd, int x, int y) { char gbuffer[16]; sprintf(gbuffer, "\x1b[%d;%dH", y, x); write(fd, gbuffer, strlen(gbuffer)); } 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; } /** * 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; 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); } // Ok, look for filename char ansifile[32] = "./hh/"; char *ap = ansifile + strlen(ansifile); while (*cp != '.') { *ap = *cp; ap++; *ap = 0; cp++; }; strcat(ansifile, ".ans"); cp++; ZF_LOGD("FILE [%s]", ansifile); if (pos) { send_file(fd, x, y, ansifile); } else { send_file(fd, ansifile); }; } 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 ZF_LOGD("GOTO (%d,%d)", x, y); sprintf(buffer, "\x1b[%d;%dH", y, x); 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; } } /** * 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); }; ZF_LOGI("no more triggers, finish it up: (%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++; } }