/* CAVE (Character Animation Viewer for Everyone) Copyright (C) 2001-2002 Ben Kibbey This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include #endif #include "common.h" #include "cave.h" void changecolor(WINDOW *win, int which) { if (has_colors() != TRUE) return; if (which) { if (++foreg > (COLORS - 1)) foreg = (backg == 0) ? 1 : 0; } else { if (++backg > (COLORS - 1)) backg = (foreg == 0) ? 1 : 0; } if (foreg == backg) changecolor(win, which); init_pair(1, foreg, backg); wbkgdset(win, COLOR_PAIR(1)); return; } void *Malloc(size_t size) { void *ptr; if ((ptr = malloc(size)) == NULL) { FATAL; endwin(); exit(errno); } return ptr; } void *Calloc(size_t num, size_t size) { void *ptr; if ((ptr = calloc(num, size)) == NULL) { FATAL; endwin(); exit(errno); } return ptr; } static void freeup(SCENE * scene) { SCENE *node, *next; FRAME *fnode, *fnext; node = scene; while (node->frameid != 0) node = node->next; for (node = node; node->frameid < frames; node = next) { next = node->next; for (fnode = node->frame; fnode; fnode = fnext) { fnext = fnode->next; free(fnode->line); free(fnode); } free(fnode); free(node); } return; } char *sepfile(char *filename) { char *str; static char tmp[FILENAME_MAX]; int i; if ((str = strrchr(filename, '/')) == NULL) return filename; for (i = 1; i < strlen(str); i++) tmp[i - 1] = str[i]; tmp[--i] = 0; return tmp; } void updatestatus(FILES * file, SCENE * scene, int row, int dir, int mark, int horizontal_offset) { unsigned min, tmin, tsec, sec, i = 0; char *tmp; if (regexdelay) i = delaylen * 1000000 / fps; tmin = (regexdelay) ? (i / 1000000) / 60 : (frames / fps) / 60; tsec = (regexdelay) ? (i / 1000000) % 60 : (frames / fps) % 60; min = (regexdelay) ? (scene->timepos * 1000000) / fps / 1000000 / 60 : (scene->frameid / fps) / 60; sec = (regexdelay) ? (scene->timepos * 1000000) / fps / 1000000 % 60 : (scene->frameid / fps) % 60; /* wmove(statusw, 0, 0); wclrtoeol(statusw); */ tmp = sepfile(file->filename); mvwprintw(statusw, 0, 0, "%i/%i: %-16s frame: %li/%li-%-2i " "%.2i:%.2i/%.2i:%.2i %i/%ix%i/%i", file->fileid, total, tmp, scene->frameid, frames, fps, min, sec, tmin, tsec, row, maxy, (maxx > COLS) ? COLS + horizontal_offset : maxx, maxx); mvwprintw(statusw, 0, COLS - (strlen(LOOP) + strlen(STEP) + 2), "%s [%c%c%c]", (stepping) ? STEP : PLAY, (dir) ? 'R' : ' ', mark, (loops) ? 'O' : ' '); update_panels(); doupdate(); return; } static void usage(const char *me) { fprintf(stderr, "Usage: %s [-hvsSqd] [-f ] [-c ] [-r ] " "[-t ] file ...\n", me); fprintf(stderr, " -f\tInitial frames-per-second (%i).\n", DEF_FPS); fprintf(stderr, " -c\tNumber of frames to jump when cueing (%i).\n", DEF_CUE); fprintf(stderr, " -r\tAlternate frame deliminator expression ('%s').\n", DEF_REGEX); fprintf(stderr, " -d\tThe specified frame deliminator is a frame delay " "value.\n"); fprintf(stderr, " -s\tGo directly into screensaver mode.\n"); fprintf(stderr, " -S\tGo directly into screensaver mode with file " "shuffle.\n"); fprintf(stderr, " -q\tQuit after keypress in screensaver.\n"); fprintf(stderr, " -t\tScreensaver timeout (%i). Zero disables.\n", DEF_TIMEOUT); fprintf(stderr, " -v\tVersion info.\n"); fprintf(stderr, " -h\tThis help text.\n"); exit(EXIT_FAILURE); } static void fullscreen(WINDOW * win) { static int x, y, posx, posy; if (panel_hidden(statusp) && !ssmode) { show_panel(statusp); wresize(win, y, x); mvwin(win, posy, posx); isfullscreen = 0; } else { hide_panel(statusp); getmaxyx(win, y, x); getbegyx(win, posy, posx); wresize(win, LINES, (maxx > COLS) ? COLS : maxx); mvwin(win, CALCPOSY(maxy), CALCPOSX(maxx)); isfullscreen = 1; } update_panels(); doupdate(); return; } static void sstimer(struct itimerval ssitv, unsigned to) { if (total > 1) { ssitv.it_interval.tv_sec = 0; ssitv.it_interval.tv_usec = 0; ssitv.it_value.tv_sec = to; ssitv.it_value.tv_usec = 0; setitimer(ITIMER_REAL, &ssitv, 0); } return; } static int loop(FILES * file, SCENE * scene) { WINDOW *animw; int err = 0, scrollpos = 0, dir = 0, z, horizontal_offset = 0; unsigned long marks = 0, marke = 0; SCENE *snode = scene, *s_marks = NULL, *s_marke = NULL, s_olds, s_olde; struct itimerval ssitv; z = (maxx > COLS) ? COLS : maxx; animw = newwin((maxy > LINES - 1) ? LINES - 1 : maxy, z, (maxy > LINES - 1) ? CALCPOSY(maxy) : CALCPOSY(maxy) - 1, CALCPOSX(z)); nodelay(animw, TRUE); keypad(animw, TRUE); leaveok(animw, TRUE); idlok(animw, TRUE); if (ssmode) { loops = 1; fullscreen(animw); if (sstimeout) sstimer(ssitv, sstimeout); } while (1) { int c, framejump = 0, n = 0; int row, col, i = 0; int x = 0, y = 0; int len = 0; FRAME *fnode; struct timeval tv; if (snode->frameid == 0) { if (ssmode && total > 1) { if (regexdelay) len = delaylen * 1000000 / fps / 1000000 % 60; else len = (frames / fps) % 60; if (sstimeout && len < sstimeout && !ssalarm) goto blah; ssalarm = 0; if (ssmode > 1) err = ERR_SHUFFLE; else err = ERR_NEXTFILE; break; } snode = snode->next; } blah: if (((snode->frameid == 1 || snode->frameid == frames) || ((marks && marke) && snode->frameid == marke)) && !loops) stepping++; if (stepping) nodelay(animw, FALSE); else nodelay(animw, TRUE); werase(animw); getmaxyx(animw, row, col); fnode = snode->frame; while (fnode->next != NULL) { int n, q = 0; if (scrollpos > i++) { fnode = fnode->next; continue; } for (n = horizontal_offset; fnode->line[n]; n++) mvwaddch(animw, y, q++, fnode->line[n]); fnode = fnode->next; y++; } n = x = 0; i = maxy - snode->rows; while (i--) mvwaddch(animw, y++, 0, ' '); if (marks && marke) i = 'M'; else if (marks && !marke) i = 'm'; else i = ' '; if (!isfullscreen) updatestatus(file, snode, row + scrollpos, dir, i, horizontal_offset); i = maxy - snode->rows; c = wgetch(animw); if (c != ERR) { if (ssmode && !stepping && ssquit) { err = ERR_QUIT; break; } } switch (c) { case 'u': changecolor(animw, 1); break; case 'U': changecolor(animw, 0); break; case 'v': message(VINFO, ANYKEY, "\n%s\n\n%s\nTerminal %s supports %d " "colors.", COPYRIGHT, curses_version(), getenv("TERM"), COLORS); break; case 'm': if (marks) *s_marks = s_olds; if (marke) *s_marke = s_olde; marks = marke = 0; marks = snode->frameid; s_marks = snode; s_olds = *snode; break; case 'M': if (!marks || snode->frameid == marks) { beep(); break; } s_olde = *snode; marke = snode->frameid; stepping = 1; if (marks < marke) { s_marke = snode; s_marks->prev = s_marke; s_marke->next = s_marks; } else { s_marke = snode; s_marke->prev = s_marks; s_marks->next = s_marke; } break; case 'c': if (marks) *s_marks = s_olds; if (marke) *s_marke = s_olde; marks = marke = 0; break; case 'r': if (dir) dir = 0; else dir = 1; break; case 'i': message(INFO, ANYKEY, "delay: %li, pos: %li, id: %li, rows: " "%i", snode->delay, snode->timepos, snode->frameid, snode->rows); break; case 'A': scrollpos = 0; break; case 'Z': scrollpos = snode->rows - row + i; /* wscrl(animw, scrollpos); */ break; case 'a': if (!scrollpos) break; scrollpos--; /* wscrl(animw, -1); */ break; case 'z': if ((snode->rows - scrollpos) + i - 1 < row) break; scrollpos++; /* wscrl(animw, 1); */ break; case 'f': fullscreen(animw); break; case 'n': if (file->fileid + 1 > total) break; err = ERR_NEXTFILE; break; case 'p': if (file->fileid - 1 < 1) break; err = ERR_PREVFILE; break; case 'g': if (snode->frameid == 0) break; if ((n = getint(GOTO, -1)) == -1 || n > frames) break; while (snode->frameid != n) snode = snode->next; framejump++; break; case 'k': case KEY_UP: if (fps + 1 > MAXFPS) break; fps++; break; case 'j': case KEY_DOWN: if (fps != 1) fps--; break; case 'L': if (horizontal_offset - 1 < 0) break; horizontal_offset--; break; case ':': if (horizontal_offset + COLS + 1 > maxx) break; horizontal_offset++; break; case 'l': case KEY_LEFT: if (snode->frameid == 0) break; if (!stepping) { n = snode->frameid - cue; if (n < 1) n += frames; while (snode->frameid != n) snode = snode->next; framejump++; break; } snode = snode->prev; break; case ';': case KEY_RIGHT: if (!stepping) { n = snode->frameid + cue; if (n > frames) { if (loops) n -= frames; else n = frames; } while (snode->frameid != n) snode = snode->next; framejump++; break; } snode = snode->next; break; case 't': if ((n = getint(CHGSSTIMEOUT, -1)) == -1) break; sstimeout = n; break; case 'S': if (total > 1) err = ERR_SHUFFLE; case 's': if (!ssmode) { if (err == ERR_SHUFFLE) ssmode++; ssmode++; loops = 1; stepping = 0; fullscreen(animw); sstimer(ssitv, sstimeout); } else { err = 0; ssmode = 0; loops = 0; stepping = 1; fullscreen(animw); sstimer(ssitv, 0); ssalarm = 0; } break; case 'o': if (ssmode) break; if (!loops) loops = 1; else loops = 0; break; case 'Q': err = ERR_QUIT; break; case 'R': err = ERR_RELOADFILE; break; case 'h': help(); break; case ' ': if (snode->frameid == 0) break; if (!stepping) stepping++; else stepping = 0; if (marks && marke) { while (snode->frameid != marks) snode = snode->next; } if (!isfullscreen) updatestatus(file, snode, row + scrollpos, dir, i, horizontal_offset); break; default: break; } if (err) { if (marks) *s_marks = s_olds; if (marke) *s_marke = s_olde; break; } if (!stepping && !framejump) { if (!regexdelay) { tv.tv_sec = 0; tv.tv_usec = 999999 / fps; } else { i = (snode->delay * 999999) / fps; tv.tv_sec = i / 999999; tv.tv_usec = i % 999999; } select(0, 0, 0, 0, &tv); if (marks > marke && dir) snode = snode->next; else if (dir || marks > marke) snode = snode->prev; else snode = snode->next; } } delwin(animw); return err; } void sighandler(int sig) { switch (sig) { case SIGALRM: ssalarm = 1; break; default: break; } return; } /* unsigned only */ static int isinteger(char *str) { int i; for (i = 0; i < strlen(str); i++) { if (isdigit(str[i]) == 0) return -1; } return atoi(str); } static long randfile(unsigned total) { return random() % total; } int main(int argc, char *argv[]) { int opt, i; char *progname; FILES *files, *fnode, *fprev; char *regexp = NULL; int err; long lastfileid = 0; char tmp[LINE_MAX]; fps = DEF_FPS; cue = DEF_CUE; sstimeout = DEF_TIMEOUT; progname = argv[0]; srandom(getpid()); signal(SIGALRM, sighandler); while ((opt = getopt(argc, argv, "hvf:Sqt:sdc:r:")) != -1) { switch (opt) { case 'q': ssquit = 1; break; case 't': if ((sstimeout = isinteger(optarg)) == -1 || sstimeout < 1) usage(progname); break; case 'S': ssmode = 2; break; case 's': ssmode = 1; break; case 'f': if ((fps = isinteger(optarg)) == -1 || fps < 1) usage(progname); break; case 'c': if ((cue = isinteger(optarg)) == -1 || cue < 1) usage(progname); break; case 'd': regexdelay = 1; break; case 'r': regexp = optarg; break; case 'v': fprintf(stdout, "%s\n%s\n", PACKAGE_STRING, COPYRIGHT); exit(EXIT_SUCCESS); case 'h': case '?': usage(progname); default: break; } } if (argc == optind) usage(progname); if (!regexp) { regexp = DEF_REGEX; regexdelay = 1; } if ((err = regcomp(®ex, regexp, REG_EXTENDED | REG_NOSUB)) != 0) { regerror(err, ®ex, tmp, sizeof(tmp)); fprintf(stderr, "regcomp(): %s\n", tmp); exit(err); } initscr(); if (start_color() == OK && has_colors() == TRUE) use_default_colors(); cbreak(); nonl(); noecho(); curs_set(0); statusw = newwin(1, COLS, LINES - 1, 0); statusp = new_panel(statusw); wbkgd(statusw, ' ' | A_REVERSE); files = Malloc(sizeof(FILES)); fnode = files; fnode->prev = NULL; i = 0; while (optind < argc) { struct stat mystat; if (stat(argv[optind], &mystat) != 0) { message(ERROR, ANYKEY, "%s: %s", argv[optind], strerror(errno)); optind++; continue; } if (!S_ISREG(mystat.st_mode)) { message(ERROR, ANYKEY, NOTAFILE, argv[optind]); optind++; continue; } fnode->filename = strdup(argv[optind]); fnode->fileid = ++total; fprev = fnode; fnode->next = Calloc(1, sizeof(FILES)); fnode = fnode->next; fnode->prev = fprev; optind++; } if (!total) { endwin(); exit(errno); } fnode->next = NULL; fnode = files; i = 0; if (total < 2 && ssmode) ssmode = 1; while (i != ERR_QUIT) { SCENE *scene = NULL; long n; maxy = maxx = 0; if ((scene = readfile(fnode->filename)) == NULL) { if (fnode->fileid == total) break; fnode = fnode->next; continue; } i = loop(fnode, scene); freeup(scene); switch (i) { case ERR_QUIT: for (fnode = files; fnode; fnode = fprev) { fprev = fnode->next; free(fnode->filename); free(fnode); } case ERR_RELOADFILE: continue; case ERR_NEXTFILE: if (fnode->next->next == NULL) { fnode = files; } else fnode = fnode->next; break; case ERR_PREVFILE: fnode = fnode->prev; break; case ERR_SHUFFLE: while (1) { if ((n = randfile(total)) != lastfileid) break; } fnode = files; while (fnode->next->next != NULL) { if (fnode->fileid == n) break; fnode = fnode->next; } lastfileid = n; break; default: break; } } del_panel(statusp); delwin(statusw); regfree(®ex); wclear(stdscr); refresh(); endwin(); exit(EXIT_SUCCESS); }