More Solaris build fixes from Steve Christensen <steve AT
[cave.git] / src / cave.c
blob14e15861b9d90e8863e52e7ecc096239d59a480b
1 /* CAVE (Character Animation Viewer for Everyone)
2 Copyright (C) 2001-2002 Ben Kibbey <bjk@luxsci.net>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <ctype.h>
21 #include <errno.h>
22 #include <string.h>
23 #include <ctype.h>
24 #include <signal.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
32 #include "common.h"
33 #include "cave.h"
35 void changecolor(WINDOW *win, int which)
37 if (has_colors() != TRUE)
38 return;
40 if (which) {
41 if (++foreg > (COLORS - 1))
42 foreg = (backg == 0) ? 1 : 0;
44 else {
45 if (++backg > (COLORS - 1))
46 backg = (foreg == 0) ? 1 : 0;
49 if (foreg == backg)
50 changecolor(win, which);
52 init_pair(1, foreg, backg);
53 wbkgdset(win, COLOR_PAIR(1));
55 return;
58 void *Malloc(size_t size)
60 void *ptr;
62 if ((ptr = malloc(size)) == NULL) {
63 FATAL;
64 endwin();
65 exit(errno);
68 return ptr;
71 void *Calloc(size_t num, size_t size)
73 void *ptr;
75 if ((ptr = calloc(num, size)) == NULL) {
76 FATAL;
77 endwin();
78 exit(errno);
81 return ptr;
84 static void freeup(SCENE * scene)
86 SCENE *node, *next;
87 FRAME *fnode, *fnext;
89 node = scene;
91 while (node->frameid != 0)
92 node = node->next;
94 for (node = node; node->frameid < frames; node = next) {
95 next = node->next;
97 for (fnode = node->frame; fnode; fnode = fnext) {
98 fnext = fnode->next;
99 free(fnode->line);
100 free(fnode);
103 free(fnode);
104 free(node);
107 return;
110 char *sepfile(char *filename)
112 char *str;
113 static char tmp[FILENAME_MAX];
114 int i;
116 if ((str = strrchr(filename, '/')) == NULL)
117 return filename;
119 for (i = 1; i < strlen(str); i++)
120 tmp[i - 1] = str[i];
122 tmp[--i] = 0;
124 return tmp;
127 void updatestatus(FILES * file, SCENE * scene, int row, int dir, int mark,
128 int horizontal_offset)
130 unsigned min, tmin, tsec, sec, i = 0;
131 char *tmp;
133 if (regexdelay)
134 i = delaylen * 1000000 / fps;
136 tmin = (regexdelay) ? (i / 1000000) / 60 : (frames / fps) / 60;
137 tsec = (regexdelay) ? (i / 1000000) % 60 : (frames / fps) % 60;
138 min = (regexdelay) ? (scene->timepos * 1000000) / fps / 1000000 / 60 :
139 (scene->frameid / fps) / 60;
140 sec = (regexdelay) ? (scene->timepos * 1000000) / fps / 1000000 % 60 :
141 (scene->frameid / fps) % 60;
143 /* wmove(statusw, 0, 0);
144 wclrtoeol(statusw); */
146 tmp = sepfile(file->filename);
148 mvwprintw(statusw, 0, 0, "%i/%i: %-16s frame: %li/%li-%-2i "
149 "%.2i:%.2i/%.2i:%.2i %i/%ix%i/%i", file->fileid, total,
150 tmp, scene->frameid, frames, fps, min, sec, tmin, tsec,
151 row, maxy, (maxx > COLS) ? COLS + horizontal_offset : maxx, maxx);
152 mvwprintw(statusw, 0, COLS - (strlen(LOOP) + strlen(STEP) + 2),
153 "%s [%c%c%c]", (stepping) ? STEP : PLAY,
154 (dir) ? 'R' : ' ', mark, (loops) ? 'O' : ' ');
156 update_panels();
157 doupdate();
159 return;
162 static void usage(const char *me)
164 fprintf(stderr, "Usage: %s [-hvsSqd] [-f <fps>] [-c <cue>] [-r <regex>] "
165 "[-t <secs>] file ...\n", me);
166 fprintf(stderr, " -f\tInitial frames-per-second (%i).\n", DEF_FPS);
167 fprintf(stderr, " -c\tNumber of frames to jump when cueing (%i).\n",
168 DEF_CUE);
169 fprintf(stderr, " -r\tAlternate frame deliminator expression ('%s').\n",
170 DEF_REGEX);
171 fprintf(stderr, " -d\tThe specified frame deliminator is a frame delay "
172 "value.\n");
173 fprintf(stderr, " -s\tGo directly into screensaver mode.\n");
174 fprintf(stderr, " -S\tGo directly into screensaver mode with file "
175 "shuffle.\n");
176 fprintf(stderr, " -q\tQuit after keypress in screensaver.\n");
177 fprintf(stderr, " -t\tScreensaver timeout (%i). Zero disables.\n",
178 DEF_TIMEOUT);
179 fprintf(stderr, " -v\tVersion info.\n");
180 fprintf(stderr, " -h\tThis help text.\n");
182 exit(EXIT_FAILURE);
185 static void fullscreen(WINDOW * win)
187 static int x, y, posx, posy;
189 if (panel_hidden(statusp) && !ssmode) {
190 show_panel(statusp);
191 wresize(win, y, x);
192 mvwin(win, posy, posx);
193 isfullscreen = 0;
195 else {
196 hide_panel(statusp);
197 getmaxyx(win, y, x);
198 getbegyx(win, posy, posx);
199 wresize(win, LINES, (maxx > COLS) ? COLS : maxx);
200 mvwin(win, CALCPOSY(maxy), CALCPOSX(maxx));
201 isfullscreen = 1;
204 update_panels();
205 doupdate();
207 return;
210 static void sstimer(struct itimerval ssitv, unsigned to)
212 if (total > 1) {
213 ssitv.it_interval.tv_sec = 0;
214 ssitv.it_interval.tv_usec = 0;
215 ssitv.it_value.tv_sec = to;
216 ssitv.it_value.tv_usec = 0;
217 setitimer(ITIMER_REAL, &ssitv, 0);
220 return;
223 static int loop(FILES * file, SCENE * scene)
225 WINDOW *animw;
226 int err = 0, scrollpos = 0, dir = 0, z, horizontal_offset = 0;
227 unsigned long marks = 0, marke = 0;
228 SCENE *snode = scene, *s_marks = NULL, *s_marke = NULL, s_olds, s_olde;
229 struct itimerval ssitv;
231 z = (maxx > COLS) ? COLS : maxx;
233 animw = newwin((maxy > LINES - 1) ? LINES - 1 : maxy, z,
234 (maxy > LINES - 1) ? CALCPOSY(maxy) : CALCPOSY(maxy) - 1,
235 CALCPOSX(z));
237 nodelay(animw, TRUE);
238 keypad(animw, TRUE);
239 leaveok(animw, TRUE);
240 idlok(animw, TRUE);
242 if (ssmode) {
243 loops = 1;
244 fullscreen(animw);
246 if (sstimeout)
247 sstimer(ssitv, sstimeout);
250 while (1) {
251 int c, framejump = 0, n = 0;
252 int row, col, i = 0;
253 int x = 0, y = 0;
254 int len = 0;
255 FRAME *fnode;
256 struct timeval tv;
258 if (snode->frameid == 0) {
259 if (ssmode && total > 1) {
260 if (regexdelay)
261 len = delaylen * 1000000 / fps / 1000000 % 60;
262 else
263 len = (frames / fps) % 60;
265 if (sstimeout && len < sstimeout && !ssalarm)
266 goto blah;
268 ssalarm = 0;
270 if (ssmode > 1)
271 err = ERR_SHUFFLE;
272 else
273 err = ERR_NEXTFILE;
275 break;
278 snode = snode->next;
281 blah:
282 if (((snode->frameid == 1 || snode->frameid == frames) ||
283 ((marks && marke) && snode->frameid == marke)) && !loops)
284 stepping++;
286 if (stepping)
287 nodelay(animw, FALSE);
288 else
289 nodelay(animw, TRUE);
291 werase(animw);
292 getmaxyx(animw, row, col);
294 fnode = snode->frame;
296 while (fnode->next != NULL) {
297 int h, q = 0;
299 if (scrollpos > i++) {
300 fnode = fnode->next;
301 continue;
304 for (h = horizontal_offset; fnode->line[h]; h++)
305 mvwaddch(animw, y, q++, fnode->line[h]);
307 fnode = fnode->next;
308 y++;
311 n = x = 0;
312 i = maxy - snode->rows;
314 while (i--)
315 mvwaddch(animw, y++, 0, ' ');
317 if (marks && marke)
318 i = 'M';
319 else if (marks && !marke)
320 i = 'm';
321 else
322 i = ' ';
324 if (!isfullscreen)
325 updatestatus(file, snode, row + scrollpos, dir, i, horizontal_offset);
327 i = maxy - snode->rows;
328 c = wgetch(animw);
330 if (c != ERR) {
331 if (ssmode && !stepping && ssquit) {
332 err = ERR_QUIT;
333 break;
337 switch (c) {
338 case 'u':
339 changecolor(animw, 1);
340 break;
341 case 'U':
342 changecolor(animw, 0);
343 break;
344 case 'v':
345 message(VINFO, ANYKEY, "\n%s\n\n%s\nTerminal %s supports %d "
346 "colors.", COPYRIGHT, curses_version(),
347 getenv("TERM"), COLORS);
348 break;
349 case 'm':
350 if (marks)
351 *s_marks = s_olds;
353 if (marke)
354 *s_marke = s_olde;
356 marks = marke = 0;
357 marks = snode->frameid;
358 s_marks = snode;
359 s_olds = *snode;
360 break;
361 case 'M':
362 if (!marks || snode->frameid == marks) {
363 beep();
364 break;
367 s_olde = *snode;
368 marke = snode->frameid;
369 stepping = 1;
371 if (marks < marke) {
372 s_marke = snode;
373 s_marks->prev = s_marke;
374 s_marke->next = s_marks;
376 else {
377 s_marke = snode;
378 s_marke->prev = s_marks;
379 s_marks->next = s_marke;
382 break;
383 case 'c':
384 if (marks)
385 *s_marks = s_olds;
387 if (marke)
388 *s_marke = s_olde;
390 marks = marke = 0;
391 break;
392 case 'r':
393 if (dir)
394 dir = 0;
395 else
396 dir = 1;
398 break;
399 case 'i':
400 message(INFO, ANYKEY, "delay: %li, pos: %li, id: %li, rows: "
401 "%i", snode->delay, snode->timepos, snode->frameid,
402 snode->rows);
403 break;
404 case 'A':
405 scrollpos = 0;
406 break;
407 case 'Z':
408 scrollpos = snode->rows - row + i;
409 /* wscrl(animw, scrollpos); */
410 break;
411 case 'a':
412 if (!scrollpos)
413 break;
415 scrollpos--;
416 /* wscrl(animw, -1); */
417 break;
418 case 'z':
419 if ((snode->rows - scrollpos) + i - 1 < row)
420 break;
422 scrollpos++;
423 /* wscrl(animw, 1); */
424 break;
425 case 'f':
426 fullscreen(animw);
427 break;
428 case 'n':
429 if (file->fileid + 1 > total)
430 break;
432 err = ERR_NEXTFILE;
433 break;
434 case 'p':
435 if (file->fileid - 1 < 1)
436 break;
438 err = ERR_PREVFILE;
439 break;
440 case 'g':
441 if (snode->frameid == 0)
442 break;
444 if ((n = getint(GOTO, -1)) == -1 || n > frames)
445 break;
447 while (snode->frameid != n)
448 snode = snode->next;
450 framejump++;
451 break;
452 case 'k':
453 case KEY_UP:
454 if (fps + 1 > MAXFPS)
455 break;
457 fps++;
458 break;
459 case 'j':
460 case KEY_DOWN:
461 if (fps != 1)
462 fps--;
463 break;
464 case 'L':
465 if (horizontal_offset - 1 < 0)
466 break;
468 horizontal_offset--;
469 break;
470 case ':':
471 if (horizontal_offset + COLS + 1 > maxx)
472 break;
474 horizontal_offset++;
475 break;
476 case 'l':
477 case KEY_LEFT:
478 if (snode->frameid == 0)
479 break;
481 if (!stepping) {
482 n = snode->frameid - cue;
484 if (n < 1)
485 n += frames;
487 while (snode->frameid != n)
488 snode = snode->next;
490 framejump++;
491 break;
494 snode = snode->prev;
495 break;
496 case ';':
497 case KEY_RIGHT:
498 if (!stepping) {
499 n = snode->frameid + cue;
501 if (n > frames) {
502 if (loops)
503 n -= frames;
504 else
505 n = frames;
508 while (snode->frameid != n)
509 snode = snode->next;
511 framejump++;
512 break;
515 snode = snode->next;
516 break;
517 case 't':
518 if ((n = getint(CHGSSTIMEOUT, -1)) == -1)
519 break;
521 sstimeout = n;
522 break;
523 case 'S':
524 if (total > 1)
525 err = ERR_SHUFFLE;
526 case 's':
527 if (!ssmode) {
528 if (err == ERR_SHUFFLE)
529 ssmode++;
531 ssmode++;
532 loops = 1;
533 stepping = 0;
534 fullscreen(animw);
535 sstimer(ssitv, sstimeout);
537 else {
538 err = 0;
539 ssmode = 0;
540 loops = 0;
541 stepping = 1;
542 fullscreen(animw);
543 sstimer(ssitv, 0);
544 ssalarm = 0;
546 break;
547 case 'o':
548 if (ssmode)
549 break;
551 if (!loops)
552 loops = 1;
553 else
554 loops = 0;
555 break;
556 case 'Q':
557 err = ERR_QUIT;
558 break;
559 case 'R':
560 err = ERR_RELOADFILE;
561 break;
562 case 'h':
563 help();
564 break;
565 case ' ':
566 if (snode->frameid == 0)
567 break;
569 if (!stepping)
570 stepping++;
571 else
572 stepping = 0;
574 if (marks && marke) {
575 while (snode->frameid != marks)
576 snode = snode->next;
579 if (!isfullscreen)
580 updatestatus(file, snode, row + scrollpos, dir, i,
581 horizontal_offset);
583 break;
584 default:
585 break;
588 if (err) {
589 if (marks)
590 *s_marks = s_olds;
592 if (marke)
593 *s_marke = s_olde;
595 break;
598 if (!stepping && !framejump) {
599 if (!regexdelay) {
600 tv.tv_sec = 0;
601 tv.tv_usec = 999999 / fps;
603 else {
604 i = (snode->delay * 999999) / fps;
606 tv.tv_sec = i / 999999;
607 tv.tv_usec = i % 999999;
610 select(0, 0, 0, 0, &tv);
612 if (marks > marke && dir)
613 snode = snode->next;
614 else if (dir || marks > marke)
615 snode = snode->prev;
616 else
617 snode = snode->next;
621 delwin(animw);
622 return err;
625 void sighandler(int sig)
627 switch (sig) {
628 case SIGALRM:
629 ssalarm = 1;
630 break;
631 default:
632 break;
635 return;
638 /* unsigned only */
639 static int isinteger(char *str)
641 int i;
643 for (i = 0; i < strlen(str); i++) {
644 if (isdigit(str[i]) == 0)
645 return -1;
648 return atoi(str);
651 static long randfile(unsigned n)
653 return random() % n;
656 int main(int argc, char *argv[])
658 int opt, i;
659 char *progname;
660 FILES *files, *fnode, *fprev;
661 char *regexp = NULL;
662 int err;
663 long lastfileid = 0;
664 char tmp[LINE_MAX];
666 fps = DEF_FPS;
667 cue = DEF_CUE;
668 sstimeout = DEF_TIMEOUT;
669 progname = argv[0];
670 srandom(getpid());
672 signal(SIGALRM, sighandler);
674 while ((opt = getopt(argc, argv, "hvf:Sqt:sdc:r:")) != -1) {
675 switch (opt) {
676 case 'q':
677 ssquit = 1;
678 break;
679 case 't':
680 if ((sstimeout = isinteger(optarg)) == -1 || sstimeout < 1)
681 usage(progname);
682 break;
683 case 'S':
684 ssmode = 2;
685 break;
686 case 's':
687 ssmode = 1;
688 break;
689 case 'f':
690 if ((fps = isinteger(optarg)) == -1 || fps < 1)
691 usage(progname);
692 break;
693 case 'c':
694 if ((cue = isinteger(optarg)) == -1 || cue < 1)
695 usage(progname);
696 break;
697 case 'd':
698 regexdelay = 1;
699 break;
700 case 'r':
701 regexp = optarg;
702 break;
703 case 'v':
704 fprintf(stdout, "%s\n%s\n", PACKAGE_STRING, COPYRIGHT);
705 exit(EXIT_SUCCESS);
706 case 'h':
707 case '?':
708 usage(progname);
709 default:
710 break;
714 if (argc == optind)
715 usage(progname);
717 if (!regexp) {
718 regexp = DEF_REGEX;
719 regexdelay = 1;
722 if ((err = regcomp(&regex, regexp, REG_EXTENDED | REG_NOSUB)) != 0) {
723 regerror(err, &regex, tmp, sizeof(tmp));
724 fprintf(stderr, "regcomp(): %s\n", tmp);
725 exit(err);
728 initscr();
730 if (start_color() == OK && has_colors() == TRUE)
731 use_default_colors();
733 cbreak();
734 nonl();
735 noecho();
736 curs_set(0);
738 statusw = newwin(1, COLS, LINES - 1, 0);
739 statusp = new_panel(statusw);
741 wbkgd(statusw, ' ' | A_REVERSE);
742 files = Malloc(sizeof(FILES));
743 fnode = files;
744 fnode->prev = NULL;
745 i = 0;
747 while (optind < argc) {
748 struct stat mystat;
750 if (stat(argv[optind], &mystat) != 0) {
751 message(ERROR, ANYKEY, "%s: %s", argv[optind], strerror(errno));
752 optind++;
753 continue;
756 if (!S_ISREG(mystat.st_mode)) {
757 message(ERROR, ANYKEY, NOTAFILE, argv[optind]);
758 optind++;
759 continue;
762 fnode->filename = strdup(argv[optind]);
763 fnode->fileid = ++total;
764 fprev = fnode;
765 fnode->next = Calloc(1, sizeof(FILES));
766 fnode = fnode->next;
767 fnode->prev = fprev;
769 optind++;
772 if (!total) {
773 endwin();
774 exit(errno);
777 fnode->next = NULL;
778 fnode = files;
779 i = 0;
781 if (total < 2 && ssmode)
782 ssmode = 1;
784 while (i != ERR_QUIT) {
785 SCENE *scene = NULL;
786 long n;
788 maxy = maxx = 0;
790 if ((scene = readfile(fnode->filename)) == NULL) {
791 if (fnode->fileid == total)
792 break;
794 fnode = fnode->next;
795 continue;
798 i = loop(fnode, scene);
799 freeup(scene);
801 switch (i) {
802 case ERR_QUIT:
803 for (fnode = files; fnode; fnode = fprev) {
804 fprev = fnode->next;
805 free(fnode->filename);
806 free(fnode);
808 case ERR_RELOADFILE:
809 continue;
810 case ERR_NEXTFILE:
811 if (fnode->next->next == NULL) {
812 fnode = files;
814 else
815 fnode = fnode->next;
817 break;
818 case ERR_PREVFILE:
819 fnode = fnode->prev;
820 break;
821 case ERR_SHUFFLE:
822 while (1) {
823 if ((n = randfile(total)) != lastfileid)
824 break;
827 fnode = files;
829 while (fnode->next->next != NULL) {
830 if (fnode->fileid == n)
831 break;
833 fnode = fnode->next;
836 lastfileid = n;
837 break;
838 default:
839 break;
843 del_panel(statusp);
844 delwin(statusw);
846 regfree(&regex);
848 wclear(stdscr);
849 refresh();
850 endwin();
852 exit(EXIT_SUCCESS);